├── assets ├── icons │ ├── desktop │ │ ├── neovim_icon.rc │ │ └── neovim.ico │ └── popup │ │ ├── NOTICE.txt │ │ ├── file.svg │ │ ├── keyword.svg │ │ ├── array.svg │ │ ├── interface.svg │ │ ├── numeric.svg │ │ ├── ruler.svg │ │ ├── constant.svg │ │ ├── enum-member.svg │ │ ├── enum.svg │ │ ├── snippet.svg │ │ ├── struct.svg │ │ ├── structure.svg │ │ ├── value.svg │ │ ├── function.svg │ │ ├── method.svg │ │ ├── reference.svg │ │ ├── constructor.svg │ │ ├── field.svg │ │ ├── event.svg │ │ ├── boolean.svg │ │ ├── misc.svg │ │ ├── class.svg │ │ ├── variable.svg │ │ ├── parameter.svg │ │ ├── color.svg │ │ ├── operator.svg │ │ ├── property.svg │ │ ├── string.svg │ │ ├── key.svg │ │ ├── module.svg │ │ └── namespace.svg ├── appicon.png ├── display │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── max-windows.svg ├── min-windows.svg ├── close-windows.svg └── max-windows-second.svg ├── test ├── test_main.cpp ├── test_hl_attr_from_object.cpp ├── test_default_colors.cpp ├── test_nvim_eval.cpp ├── test_object_deserialization_msgpack.cpp ├── test_nvim_set_var.cpp └── test_object.cpp ├── scripts ├── windows │ ├── test.ps1 │ ├── build-release.ps1 │ ├── build-debug.ps1 │ └── package.ps1 └── linux │ ├── build-debug.sh │ ├── build-release.sh │ └── package.sh ├── .gitmodules ├── src ├── decide_renderer.hpp ├── fontdesc.hpp ├── types.hpp ├── input.hpp ├── config.cpp ├── config.hpp ├── animation.hpp ├── platform │ └── windows │ │ ├── textformat.hpp │ │ ├── textformat.cpp │ │ ├── d2deditor.hpp │ │ └── direct2dpaintgrid.hpp ├── constants.hpp ├── mouse.hpp ├── animation.cpp ├── scalers.hpp ├── qeditor.hpp ├── font.hpp ├── lru.hpp ├── nvim_utils.hpp ├── qpaintgrid.hpp ├── cmdline.hpp ├── titlebar.hpp ├── window.hpp ├── utils.hpp ├── hlstate.cpp ├── grid.hpp ├── hlstate.hpp ├── grid.cpp ├── main.cpp ├── qt_editorui_base.hpp ├── cursor.hpp ├── editor_base.hpp ├── object.cpp ├── object.hpp ├── qeditor.cpp ├── titlebar.cpp ├── nvim.cpp └── input.cpp ├── .gitignore ├── vcpkg.json ├── cmake └── Findmsgpack.cmake ├── LICENSE ├── resources.qrc ├── README.md ├── vim └── doc │ └── tags ├── BUILDING.md └── CMakeLists.txt /assets/icons/desktop/neovim_icon.rc: -------------------------------------------------------------------------------- 1 | NEOVIM_ICON ICON "neovim.ico" -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /assets/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/appicon.png -------------------------------------------------------------------------------- /assets/display/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/display/1.png -------------------------------------------------------------------------------- /assets/display/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/display/2.png -------------------------------------------------------------------------------- /assets/display/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/display/3.png -------------------------------------------------------------------------------- /assets/display/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/display/4.png -------------------------------------------------------------------------------- /scripts/windows/test.ps1: -------------------------------------------------------------------------------- 1 | cmake --build build --target nvui_test 2 | ./build/nvui_test 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg"] 2 | path = vcpkg 3 | url = https://github.com/microsoft/vcpkg.git 4 | -------------------------------------------------------------------------------- /assets/icons/desktop/neovim.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohit-px2/nvui/HEAD/assets/icons/desktop/neovim.ico -------------------------------------------------------------------------------- /src/decide_renderer.hpp: -------------------------------------------------------------------------------- 1 | /// Decide the rendering protocol. 2 | 3 | #ifdef Q_OS_WIN 4 | #define USE_DIRECT2D 1 5 | #else 6 | #define USE_QPAINTER 1 7 | #endif 8 | -------------------------------------------------------------------------------- /scripts/linux/build-debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cmake -B build . \ 3 | -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake \ 4 | -DCMAKE_BUILD_TYPE=Debug 5 | cmake --build build --config Debug 6 | -------------------------------------------------------------------------------- /assets/icons/popup/NOTICE.txt: -------------------------------------------------------------------------------- 1 | The icons are taken from Microsoft's vscode-codicons Github repository. 2 | You can find the original repository for the icons at: 3 | https://github.com/microsoft/vscode-codicons 4 | -------------------------------------------------------------------------------- /scripts/linux/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cmake -B build . \ 3 | -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake \ 4 | -DCMAKE_BUILD_TYPE=Release 5 | cmake --build build --config Release 6 | -------------------------------------------------------------------------------- /scripts/linux/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd build 3 | mkdir packaged 4 | mkdir packaged/bin 5 | cp nvui packaged/bin 6 | cp -r ../assets packaged 7 | rm -rf packaged/assets/display 8 | cp -r ../vim packaged 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .exe 2 | .txt 3 | build/ 4 | build-mingw/ 5 | target/ 6 | .cache/ 7 | .vscode/ 8 | .vs/ 9 | out/ 10 | compile_commands.json 11 | CMakeLists.txt.user 12 | *.cmd 13 | .ccls-cache/ 14 | CMakeSettings.json 15 | .ignore 16 | -------------------------------------------------------------------------------- /assets/icons/popup/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/max-windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/popup/keyword.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/array.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/interface.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/numeric.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/ruler.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/fontdesc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_FONTDESC_HPP 2 | #define NVUI_FONTDESC_HPP 3 | 4 | #include 5 | #include "hlstate.hpp" 6 | 7 | struct FontDesc 8 | { 9 | std::string name; 10 | double point_size; 11 | FontOptions base_options; 12 | }; 13 | 14 | #endif // NVUI_FONTDESC_HPP 15 | -------------------------------------------------------------------------------- /assets/min-windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/popup/constant.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/enum-member.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/enum.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/snippet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/struct.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/structure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/value.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/function.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/method.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/reference.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/constructor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/close-windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/icons/popup/event.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/max-windows-second.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_TYPES_HPP 2 | #define NVUI_TYPES_HPP 3 | 4 | #include 5 | 6 | using i8 = std::int8_t; 7 | using i16 = std::int16_t; 8 | using i32 = std::int32_t; 9 | using i64 = std::int64_t; 10 | using u8 = std::uint8_t; 11 | using u16 = std::uint16_t; 12 | using u32 = std::uint32_t; 13 | using u64 = std::uint64_t; 14 | #endif // NVUI_TYPES_HPP 15 | -------------------------------------------------------------------------------- /scripts/windows/build-release.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$cc = "clang", 3 | [string]$cxx = "clang++", 4 | [string]$gen = "Ninja" 5 | ) 6 | $generator = '"{0}"' -f $gen 7 | $cmd = Write-Output "cmake . -B build -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator" 8 | cmd /C $cmd 9 | cmake --build build --config Release 10 | -------------------------------------------------------------------------------- /assets/icons/popup/boolean.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/windows/build-debug.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$cc = "clang", 3 | [string]$cxx = "clang++", 4 | [string]$gen = "Ninja" 5 | ) 6 | $generator = '"{0}"' -f $gen 7 | $cmd = Write-Output "cmake . -B build -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator -DBUILD_TESTS=ON" 8 | cmd /C $cmd 9 | cmake --build build --config Debug 10 | -------------------------------------------------------------------------------- /assets/icons/popup/misc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/class.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/variable.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/parameter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", 3 | "name": "nvui", 4 | "version-string": "0.1.4", 5 | "dependencies": [ 6 | "fmt", 7 | "msgpack", 8 | "qt5-base", 9 | "qt5-svg", 10 | "boost-process", 11 | "boost-container" 12 | ], 13 | "features": { 14 | "tests": { 15 | "description": "Build tests", 16 | "dependencies": [ "catch2" ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cmake/Findmsgpack.cmake: -------------------------------------------------------------------------------- 1 | include(CheckLibraryExists) 2 | 3 | find_path(MSGPACK_INCLUDE_DIR 4 | NAMES msgpack.h 5 | ) 6 | 7 | find_library(MSGPACK_LIBRARY 8 | NAMES msgpack msgpackc msgpackc.lib 9 | ) 10 | 11 | mark_as_advanced(MSGPACK_INCLUDE_DIR MSGPACK_LIBRARY) 12 | set(MSGPACK_LIBRARIES ${MSGPACK_LIBRARY}) 13 | set(MSGPACK_INCLUDE_DIRS ${MSGPACK_INCLUDE_DIR}) 14 | 15 | include(FindPackageHandleStandardArgs) 16 | find_package_handle_standard_args(Msgpack DEFAULT_MSG 17 | MSGPACK_LIBRARY MSGPACK_INCLUDE_DIR) 18 | -------------------------------------------------------------------------------- /src/input.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_INPUT_HPP 2 | #define NVUI_INPUT_HPP 3 | 4 | #include 5 | #include 6 | 7 | /// Converts a key event to Neovim input string. 8 | /// Most of this code is taken from Neovim-Qt, 9 | /// see 10 | /// https://github.com/equalsraf/neovim-qt/blob/master/src/gui/input.hpp 11 | /// and 12 | /// https://github.com/equalsraf/neovim-qt/blob/master/src/gui/input.cpp 13 | /// Changed a little bit to return an std::string instead of a QString 14 | std::string convert_key(const QKeyEvent& ev); 15 | 16 | #endif // NVUI_INPUT_HPP 17 | -------------------------------------------------------------------------------- /scripts/windows/package.ps1: -------------------------------------------------------------------------------- 1 | ./scripts/windows/build-release.ps1 2 | cd build 3 | mkdir nvui 4 | cd nvui 5 | mkdir bin 6 | cd .. 7 | (gci -Path ./* -Include nvui.exe, *.dll, *.conf).fullname | foreach {Copy-Item -Force -Path $_ -Destination nvui/bin} 8 | Copy-Item -Force -Recurse -Path plugins -Destination nvui/bin 9 | Copy-Item -Force -Recurse -Path ../assets -Destination nvui 10 | Copy-Item -Force -Recurse -Path ../vim -Destination nvui 11 | Remove-Item -Force -Recurse -Path nvui/assets/display 12 | Compress-Archive -Force -Path nvui -DestinationPath nvui.zip 13 | Move-Item -Force -Path nvui.zip -Destination ../ 14 | cd .. 15 | -------------------------------------------------------------------------------- /assets/icons/popup/color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "utils.hpp" 3 | 4 | std::unique_ptr Config::settings_ptr = nullptr; 5 | 6 | void Config::init() 7 | { 8 | const auto ini_path = normalize_path("nvui-config.ini"); 9 | if (!settings_ptr) 10 | { 11 | settings_ptr = std::make_unique(ini_path, QSettings::IniFormat); 12 | } 13 | } 14 | 15 | QVariant Config::get(const QString& key, const QVariant& default_val) 16 | { 17 | return settings_ptr->value(key, default_val); 18 | } 19 | 20 | void Config::set(const QString& key, const QVariant& value) 21 | { 22 | settings_ptr->setValue(key, value); 23 | } 24 | 25 | bool Config::is_set(const QString& key) 26 | { 27 | return settings_ptr->contains(key); 28 | } 29 | 30 | void Config::remove_key(const QString& key) 31 | { 32 | settings_ptr->remove(key); 33 | } 34 | 35 | void Config::clear() 36 | { 37 | settings_ptr->clear(); 38 | } 39 | -------------------------------------------------------------------------------- /assets/icons/popup/operator.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/property.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Rohit Pradhan 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /test/test_hl_attr_from_object.cpp: -------------------------------------------------------------------------------- 1 | #include "object.hpp" 2 | #include "hlstate.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | TEST_CASE("hl_attr_from_object works", "[hl_attr_from_object]") 9 | { 10 | SECTION("Works for standard data examples taken from Neovim messages") 11 | { 12 | uint64 rgb = 16753826; 13 | Object o = ObjectArray { 14 | uint64(107), 15 | ObjectMap { 16 | {"italic", true}, 17 | {"foreground", rgb} 18 | }, 19 | ObjectMap {}, 20 | ObjectArray { 21 | ObjectMap { 22 | {"kind", std::string("syntax")}, 23 | {"hi_name", std::string("TSParameter")}, 24 | {"id", uint64(107)} 25 | } 26 | } 27 | }; 28 | const HLAttr resulting_attr = hl::hl_attr_from_object(o); 29 | REQUIRE(resulting_attr.hl_id == 107); 30 | REQUIRE(!resulting_attr.bg().has_value()); 31 | REQUIRE(resulting_attr.fg().has_value()); 32 | REQUIRE(resulting_attr.fg().value().to_uint32() == rgb); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/config.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_SETTINGS_HPP 2 | #define NVUI_SETTINGS_HPP 3 | 4 | #include 5 | #include 6 | 7 | // External settings that aren't really runtime-configurable. 8 | // This means things like default UI settings (multigrid being the biggest 9 | // one, since it is not changeable at runtime), as well as startup 10 | // position and size. 11 | class Config 12 | { 13 | public: 14 | // Initialize config (load config file) 15 | // This must be done AFTER the QApplication is created 16 | // For a config file to be loaded it must be in the executable directory 17 | // under the name "nvui-config.ini". 18 | static void init(); 19 | // init() MUST be called before the following functions 20 | static QVariant get(const QString& key, const QVariant& def_val = QVariant()); 21 | static void set(const QString& key, const QVariant& value); 22 | static bool is_set(const QString& key); 23 | static void remove_key(const QString& key); 24 | static void clear(); 25 | private: 26 | static std::unique_ptr settings_ptr; 27 | }; 28 | 29 | #endif // NVUI_SETTINGS_HPP 30 | -------------------------------------------------------------------------------- /src/animation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_ANIMATION_HPP 2 | #define NVUI_ANIMATION_HPP 3 | 4 | #include 5 | #include 6 | 7 | class Animation 8 | { 9 | public: 10 | Animation(); 11 | bool is_running() const; 12 | /// Will it do anything if you start it 13 | bool is_valid() const; 14 | void reset(); 15 | void start(); 16 | void set_duration(double secs); 17 | void set_interval(int ms); 18 | template 19 | void on_update(Func&& f); 20 | double percent_finished() const; 21 | double duration() const; 22 | int interval() const; 23 | void on_stop(std::function stopfunc); 24 | void stop(); 25 | private: 26 | void update_dt(); 27 | QElapsedTimer elapsed_timer; 28 | QTimer timer; 29 | double animation_duration; 30 | double time_left; 31 | std::function stop_func; 32 | }; 33 | 34 | template 35 | void Animation::on_update(Func&& f) 36 | { 37 | timer.callOnTimeout([f = std::forward(f), this] { 38 | update_dt(); 39 | if (time_left < 0) 40 | { 41 | stop(); 42 | return; 43 | } 44 | f(); 45 | elapsed_timer.start(); 46 | }); 47 | } 48 | #endif // NVUI_ANIMATION_HPP 49 | -------------------------------------------------------------------------------- /src/platform/windows/textformat.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_PLATFORM_WINDOWS_TEXTFORMAT_HPP 2 | #define NVUI_PLATFORM_WINDOWS_TEXTFORMAT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "hlstate.hpp" 9 | 10 | struct TextFormat 11 | { 12 | TextFormat( 13 | IDWriteFactory* factory, 14 | const std::wstring& name, 15 | float pointsize, 16 | float dpi, 17 | FontOpts default_weight, 18 | FontOpts default_style 19 | ); 20 | IDWriteTextFormat* font_for(const FontOptions& fo) const; 21 | bool is_monospace() const; 22 | Microsoft::WRL::ComPtr reg = nullptr; 23 | Microsoft::WRL::ComPtr bold = nullptr; 24 | Microsoft::WRL::ComPtr bolditalic = nullptr; 25 | Microsoft::WRL::ComPtr italic = nullptr; 26 | private: 27 | bool is_mono = true; 28 | }; 29 | 30 | DWRITE_FONT_STYLE dwrite_style(const FontOpts& fo); 31 | DWRITE_FONT_WEIGHT dwrite_weight(const FontOpts& fo); 32 | DWRITE_HIT_TEST_METRICS metrics_for( 33 | std::wstring_view text, 34 | IDWriteFactory* factory, 35 | IDWriteTextFormat* text_format 36 | ); 37 | 38 | #endif // NVUI_PLATFORM_WINDOWS_TEXTFORMAT_HPP 39 | -------------------------------------------------------------------------------- /test/test_default_colors.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "hlstate.hpp" 4 | #include "object.hpp" 5 | #include 6 | 7 | TEST_CASE("default_colors are set properly", "[default_colors_set]") 8 | { 9 | using namespace std::string_literals; 10 | HLState hl_state; 11 | // Params of "default_colors_set" 12 | SECTION("default_colors_set: Test #1") 13 | { 14 | std::stringstream ss; 15 | msgpack::packer packer {ss}; 16 | packer.pack_array(5); 17 | packer.pack(16777215); // rgb fg 18 | packer.pack(0); // rgb bg 19 | packer.pack(16711680); // rgb special 20 | packer.pack(0); // cterm fg 21 | packer.pack(0); // cterm bg 22 | const auto oh = msgpack::unpack(ss.str().data(), ss.str().size()); 23 | const msgpack::object& obj = oh.get(); 24 | auto parsed = Object::parse(obj); 25 | hl_state.default_colors_set(parsed); 26 | const auto& def = hl_state.default_colors_get(); 27 | REQUIRE(def.fg()); 28 | REQUIRE(def.bg()); 29 | REQUIRE(def.sp()); 30 | REQUIRE(def.fg()->to_uint32() == 16777215); 31 | REQUIRE(def.bg()->to_uint32() == 0); 32 | REQUIRE(def.sp()->to_uint32() == 16711680); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_CONSTANTS_HPP 2 | #define NVUI_CONSTANTS_HPP 3 | 4 | #include 5 | #include "utils.hpp" 6 | 7 | namespace constants 8 | { 9 | inline const QString& picon_fp() 10 | { 11 | static const QString picon_fp = ":/assets/icons/popup/"; 12 | return picon_fp; 13 | } 14 | 15 | inline const QString& appicon() 16 | { 17 | static const QString icon = ":/assets/appicon.png"; 18 | return icon; 19 | } 20 | 21 | inline const QString& maxicon() 22 | { 23 | static const QString maxicon = ":/assets/max-windows.svg"; 24 | return maxicon; 25 | } 26 | 27 | inline const QString& maxicon_second() 28 | { 29 | static const QString maxicon_second = ":/assets/max-windows-second.svg"; 30 | return maxicon_second; 31 | } 32 | 33 | inline const QString& minicon() 34 | { 35 | static const QString minicon = ":/assets/min-windows.svg"; 36 | return minicon; 37 | } 38 | 39 | inline const QString& closeicon() 40 | { 41 | static const QString closeicon = ":/assets/close-windows.svg"; 42 | return closeicon; 43 | } 44 | 45 | /// Directory for runtime files (doc) 46 | inline const QString& script_dir() 47 | { 48 | static const QString dir = normalize_path("../vim"); 49 | return dir; 50 | } 51 | 52 | } // namespace constants 53 | #endif 54 | -------------------------------------------------------------------------------- /assets/icons/popup/string.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/mouse.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_MOUSE_HPP 2 | #define NVUI_MOUSE_HPP 3 | 4 | #include 5 | struct Mouse 6 | { 7 | Mouse() = default; 8 | Mouse(int interval) 9 | : click_timer(), 10 | gridid(), 11 | row(), col(), 12 | click_interval(interval) 13 | { 14 | click_timer.setSingleShot(true); 15 | click_timer.setInterval(interval); 16 | click_timer.callOnTimeout([&] { 17 | reset_click(); 18 | }); 19 | } 20 | void set_position(int grid, int x, int y) 21 | { 22 | gridid = grid; 23 | row = y; 24 | col = x; 25 | } 26 | void button_clicked(Qt::MouseButton b) 27 | { 28 | if (cur_button == b) 29 | { 30 | ++click_count; 31 | start_timer(); 32 | } 33 | else 34 | { 35 | reset_click(); 36 | cur_button = b; 37 | click_count = 1; 38 | start_timer(); 39 | } 40 | } 41 | void reset_click() 42 | { 43 | click_timer.stop(); 44 | click_count = 0; 45 | cur_button = Qt::NoButton; 46 | } 47 | void start_timer() 48 | { 49 | if (click_timer.isActive()) return; 50 | click_timer.start(); 51 | } 52 | int click_count = 0; 53 | Qt::MouseButton cur_button = Qt::NoButton; 54 | QTimer click_timer; 55 | int gridid = 0; 56 | int row = 0; 57 | int col = 0; 58 | int click_interval; 59 | }; 60 | 61 | #endif // NVUI_MOUSE_HPP 62 | -------------------------------------------------------------------------------- /test/test_nvim_eval.cpp: -------------------------------------------------------------------------------- 1 | #include "nvim.hpp" 2 | #include "utils.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "object.hpp" 10 | 11 | using namespace std::chrono_literals; 12 | 13 | TEST_CASE("nvim_eval callbacks work", "[eval_cb]") 14 | { 15 | Nvim nvim; 16 | REQUIRE(nvim.running()); 17 | SECTION("Evaluating math") 18 | { 19 | std::atomic done = false; 20 | nvim.eval_cb("1 + 2", [&](Object res, Object err) { 21 | REQUIRE(err.is_null()); 22 | REQUIRE(res.try_convert()); 23 | REQUIRE(*res.try_convert() == 3); 24 | done = true; 25 | }); 26 | wait_for_value(done, true); 27 | } 28 | SECTION("Can evaluate variables") 29 | { 30 | std::atomic done = false; 31 | nvim.eval_cb("stdpath('config')", [&](Object res, Object err) { 32 | REQUIRE(err.is_null()); 33 | REQUIRE(res.string()); 34 | done = true; 35 | }); 36 | wait_for_value(done, true); 37 | } 38 | SECTION("Will send errors in the 'err' parameter") 39 | { 40 | std::atomic done = false; 41 | nvim.eval_cb("stdpath", [&](Object res, Object err) { 42 | REQUIRE(res.is_null()); 43 | REQUIRE(!err.is_null()); 44 | done = true; 45 | }); 46 | wait_for_value(done, true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/animation.cpp: -------------------------------------------------------------------------------- 1 | #include "animation.hpp" 2 | 3 | Animation::Animation() 4 | : elapsed_timer(), 5 | timer(), 6 | animation_duration(-1.0), 7 | time_left(-1.0), 8 | stop_func() 9 | { 10 | } 11 | 12 | bool Animation::is_running() const { return timer.isActive(); } 13 | bool Animation::is_valid() const { return animation_duration >= 0; } 14 | void Animation::set_duration(double dur) { animation_duration = dur; } 15 | void Animation::set_interval(int ms) { timer.setInterval(ms); } 16 | 17 | void Animation::reset() 18 | { 19 | elapsed_timer.invalidate(); 20 | timer.stop(); 21 | stop_func(); 22 | animation_duration = -1.0; 23 | time_left = -1.0; 24 | } 25 | 26 | void Animation::stop() 27 | { 28 | timer.stop(); 29 | if (stop_func) stop_func(); 30 | } 31 | 32 | void Animation::start() 33 | { 34 | if (animation_duration < 0) return; 35 | time_left = animation_duration; 36 | timer.start(); 37 | elapsed_timer.start(); 38 | } 39 | 40 | double Animation::percent_finished() const 41 | { 42 | return 1 - (time_left / animation_duration); 43 | } 44 | 45 | void Animation::update_dt() 46 | { 47 | time_left -= double(timer.interval()) / 1000.0; 48 | } 49 | 50 | void Animation::on_stop(std::function stopfunc) 51 | { 52 | stop_func = std::move(stopfunc); 53 | } 54 | 55 | double Animation::duration() const { return animation_duration; } 56 | int Animation::interval() const { return timer.interval(); } 57 | -------------------------------------------------------------------------------- /test/test_object_deserialization_msgpack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "object.hpp" 3 | #include 4 | #include 5 | 6 | TEST_CASE("Object correctly deserialies from msgpack") 7 | { 8 | SECTION("Deserializing an array of u64s") 9 | { 10 | msgpack::sbuffer pack_buf; 11 | std::vector x {5, 2, 3, 4, 6}; 12 | msgpack::pack(pack_buf, x); 13 | std::string_view sv {pack_buf.data(), pack_buf.size()}; 14 | std::size_t offset = 0; 15 | auto parsed = Object::from_msgpack(sv, offset); 16 | REQUIRE(offset == sv.size()); // Only one object 17 | REQUIRE(parsed.has()); 18 | REQUIRE(parsed.array()->size() == x.size()); 19 | const auto& arr = parsed.get(); 20 | for(std::size_t i = 0; i < arr.size(); ++i) 21 | { 22 | auto opt = arr[i].try_convert(); 23 | REQUIRE(opt); 24 | REQUIRE(*opt == x[i]); 25 | } 26 | } 27 | SECTION("Error parsing") 28 | { 29 | msgpack::sbuffer sbuf; 30 | std::vector x {1, 2, 3, 4, 5, 6}; 31 | msgpack::pack(sbuf, x); 32 | // Reducing the size of the string view by 1 33 | // causes it to not read the "array end" message, 34 | // causing an error (it tries to read more elements). 35 | std::string_view sv(sbuf.data(), sbuf.size() - 1); 36 | std::size_t offset = 0; 37 | Object o = Object::from_msgpack(sv, offset); 38 | REQUIRE(o.is_err()); 39 | REQUIRE(o.get().msg == "Insufficient Bytes"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/icons/popup/key.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_nvim_set_var.cpp: -------------------------------------------------------------------------------- 1 | #include "nvim.hpp" 2 | #include "utils.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | TEST_CASE("nvim_set_var sets variables properly", "[nvim_set_var]") 12 | { 13 | using std::this_thread::sleep_for; 14 | using namespace std::chrono_literals; 15 | // One thing to note: nvim_set_var only works for setting strings and ints 16 | // (as is said in the header file). However it's just a template method 17 | // so adding more types is as easy as adding more declarations in the cpp file. 18 | Nvim nvim; 19 | REQUIRE(nvim.running()); 20 | SECTION("nvim_set_var works for ints") 21 | { 22 | std::atomic done = false; 23 | nvim.set_var("uniquevariable", 253); 24 | // Neovim sets a global (g:) variable with the name we gave, 25 | // so we can get the result from an nvim_eval command. 26 | nvim.eval_cb("g:uniquevariable", [&](Object res, Object err) { 27 | REQUIRE(err.is_null()); 28 | auto result = res.try_convert(); 29 | REQUIRE(result); 30 | REQUIRE(*result == 253); 31 | done = true; 32 | }); 33 | wait_for_value(done, true); 34 | } 35 | SECTION("nvim_set_var works for strings") 36 | { 37 | std::atomic done = false; 38 | nvim.set_var("uniquevariabletwo", std::string("doesthiswork")); 39 | nvim.eval_cb("g:uniquevariabletwo", [&](Object res, Object err) { 40 | REQUIRE(err.is_null()); 41 | auto str = res.string(); 42 | REQUIRE(str); 43 | REQUIRE(*str == "doesthiswork"); 44 | done = true; 45 | }); 46 | wait_for_value(done, true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | assets/icons/popup/array.svg 4 | assets/icons/popup/boolean.svg 5 | assets/icons/popup/class.svg 6 | assets/icons/popup/color.svg 7 | assets/icons/popup/constant.svg 8 | assets/icons/popup/constructor.svg 9 | assets/icons/popup/enum-member.svg 10 | assets/icons/popup/enum.svg 11 | assets/icons/popup/event.svg 12 | assets/icons/popup/field.svg 13 | assets/icons/popup/file.svg 14 | assets/icons/popup/function.svg 15 | assets/icons/popup/interface.svg 16 | assets/icons/popup/key.svg 17 | assets/icons/popup/keyword.svg 18 | assets/icons/popup/method.svg 19 | assets/icons/popup/misc.svg 20 | assets/icons/popup/module.svg 21 | assets/icons/popup/namespace.svg 22 | assets/icons/popup/numeric.svg 23 | assets/icons/popup/operator.svg 24 | assets/icons/popup/parameter.svg 25 | assets/icons/popup/property.svg 26 | assets/icons/popup/reference.svg 27 | assets/icons/popup/ruler.svg 28 | assets/icons/popup/snippet.svg 29 | assets/icons/popup/string.svg 30 | assets/icons/popup/struct.svg 31 | assets/icons/popup/structure.svg 32 | assets/icons/popup/value.svg 33 | assets/icons/popup/variable.svg 34 | assets/appicon.png 35 | assets/max-windows.svg 36 | assets/min-windows.svg 37 | assets/close-windows.svg 38 | assets/max-windows-second.svg 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/scalers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_SCALERS_HPP 2 | #define NVUI_SCALERS_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace scalers 8 | { 9 | // Follows the same idea 10 | // as Neovide's "ease" functions. time is between 0 - 1, 11 | // we can use some exponents to change the delta time so that 12 | // transition speed is not the same throughout. 13 | using time_scaler = float (*)(float); 14 | inline float oneminusexpo2negative10(float t) 15 | { 16 | // Taken from Neovide's "animation_utils.rs", 17 | // (specifically the "ease_out_expo" function). 18 | return 1.0f - std::pow(2.0, -10.0f * t); 19 | } 20 | inline float cube(float t) 21 | { 22 | return t * t * t; 23 | } 24 | inline float accel_continuous(float t) 25 | { 26 | return t * t * t * t; 27 | } 28 | inline float fast_start(float t) 29 | { 30 | return std::pow(t, 1.0/9.0); 31 | } 32 | inline float quadratic(float t) 33 | { 34 | return t * t; 35 | } 36 | inline float identity(float t) 37 | { 38 | return t; 39 | } 40 | inline float root(float t) 41 | { 42 | return std::sqrt(t); 43 | } 44 | /// Update this when a new scaler is added. 45 | inline const std::unordered_map& 46 | scalers() 47 | { 48 | static const std::unordered_map scaler_map { 49 | {"expo", oneminusexpo2negative10}, 50 | {"cube", cube}, 51 | {"fourth", accel_continuous}, 52 | {"fast_start", fast_start}, 53 | {"quad", quadratic}, 54 | {"identity", identity}, 55 | {"squareroot", root} 56 | }; 57 | return scaler_map; 58 | } 59 | 60 | inline std::vector 61 | scaler_names() 62 | { 63 | std::vector names; 64 | names.reserve(scalers::scalers().size()); 65 | for(const auto& p : scalers::scalers()) names.push_back(p.first); 66 | return names; 67 | } 68 | } 69 | 70 | #endif // NVUI_SCALERS_HPP 71 | -------------------------------------------------------------------------------- /assets/icons/popup/module.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/popup/namespace.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/qeditor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_QEDITOR_HPP 2 | #define NVUI_QEDITOR_HPP 3 | 4 | #include "qt_editorui_base.hpp" 5 | #include 6 | 7 | class Font; 8 | 9 | class QEditor : public QWidget, public QtEditorUIBase 10 | { 11 | Q_OBJECT 12 | using Base = QtEditorUIBase; 13 | public: 14 | QEditor( 15 | int cols, 16 | int rows, 17 | std::unordered_map capabilities, 18 | std::string nvim_path, 19 | std::vector nvim_args, 20 | QWidget* parent = nullptr 21 | ); 22 | ~QEditor() override; 23 | void setup() override; 24 | const auto& fallback_list() const { return fonts; } 25 | const auto& main_font() const { return first_font; } 26 | u32 font_for_ucs(u32 ucs); 27 | signals: 28 | void font_changed(); 29 | protected: 30 | void linespace_changed(float new_ls) override; 31 | void charspace_changed(float new_cs) override; 32 | protected: 33 | void resizeEvent(QResizeEvent* event) override; 34 | void mousePressEvent(QMouseEvent* event) override; 35 | void mouseReleaseEvent(QMouseEvent* event) override; 36 | void wheelEvent(QWheelEvent* event) override; 37 | void dropEvent(QDropEvent* event) override; 38 | void dragEnterEvent(QDragEnterEvent* event) override; 39 | void inputMethodEvent(QInputMethodEvent* event) override; 40 | void mouseMoveEvent(QMouseEvent* event) override; 41 | void paintEvent(QPaintEvent* event) override; 42 | void keyPressEvent(QKeyEvent* event) override; 43 | void focusInEvent(QFocusEvent* event) override; 44 | void focusOutEvent(QFocusEvent* event) override; 45 | bool focusNextPrevChild(bool) override { return false; } 46 | private: 47 | u32 calc_fallback_index(u32 ucs); 48 | std::unique_ptr popup_new() override; 49 | std::unique_ptr cmdline_new() override; 50 | void redraw() override; 51 | void create_grid(u32 x, u32 y, u32 w, u32 h, u64 id) override; 52 | void set_fonts(std::span fonts) override; 53 | private: 54 | std::unordered_map fallback_indices; 55 | void update_font_metrics(); 56 | QFont first_font; 57 | std::vector fonts; 58 | }; 59 | 60 | #endif // NVUI_QEDITOR_HPP 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NVUI 2 | ![first](assets/display/1.png) 3 | 4 | ## Featuring 5 |
    6 |
  • Custom Titlebar (toggleable, see :h nvui-titlebar)
  • 7 |
8 |
    9 |
  • External Command Line (nvui --ext_cmdline)
  • 10 |
  • Configurable positions, sizes, colors, border width, border color, etc.
  • 11 |
  • For more information see :h nvui-cmdline
  • 12 |
13 | 14 | ![ext_cmdline](assets/display/2.png) 15 |
    16 |
  • External Popup Menu (nvui --ext_popupmenu)
  • 17 |
  • Configurable colors, background colors, icon sizes, alignment, border width, border color, etc.
  • 18 |
  • For more information see :h nvui-popup
  • 19 |
20 | 21 | ![ext_popupmenu](assets/display/3.png) 22 |
    23 |
  • Cross-platform Qt, DirectWrite on Windows
  • 24 |
25 |
    26 |
  • Font Fallback (see guifont in the bottom left)
  • 27 |
  • Configurable through "set guifont"
  • 28 |
29 | 30 | ![fallback](assets/display/4.png) 31 | 32 | 33 |
    34 |
  • External Multigrid Support (:h nvui-multigrid)
  • 35 |
  • nvui --ext_multigrid
  • 36 |
  • Smooth Scrolling
  • 37 | 38 | [Smooth scrolling Demo](https://user-images.githubusercontent.com/64917719/131388187-7807833e-0fce-4db2-a25e-1e6ac7d48aed.mp4) 39 | 40 |
  • Animated Windows
  • 41 | 42 | [Animated windows Demo](https://user-images.githubusercontent.com/64917719/131388158-84783f63-0e43-45e7-8b9d-7e0d85649cb3.mp4) 43 | 44 |
  • Animated Cursor (no VFX... yet)
  • 45 |
  • Customizable cursor height (see :h NvuiCaretExtendTop 46 | and :h NvuiCaretExtendBottom) 47 |
      48 |
    • Ex. :NvuiCaretExtendTop 200, :NvuiCaretExtendBottom 100 (see below)
    • 49 |
    50 |
51 | 52 | [Cursor extending Demo](https://user-images.githubusercontent.com/64917719/131388133-379a32ae-1b36-4dc2-aee9-4303c20dc764.mp4) 53 | 54 | ## Documentation 55 | :h nvui (vim/doc/nvui.txt) 56 | 57 | ## Build Instructions 58 | See [the wiki](https://github.com/rohit-px2/nvui/wiki#build-instructions) 59 | 60 | ## Configuration (Command Line Arguments / Vim Configuration) 61 | See [the wiki](https://github.com/rohit-px2/nvui/wiki#configuration-options) 62 | and `:h nvui` for Vim command documentation. 63 | -------------------------------------------------------------------------------- /src/font.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_FONT_HPP 2 | #define NVUI_FONT_HPP 3 | #include 4 | #include 5 | #include 6 | #include "hlstate.hpp" 7 | 8 | // Font class created with the expectation that it will be modified 9 | // very rarely, but read very frequently 10 | // Caches bold, italic, and bolditalic versions of fonts 11 | // to reduce the amount of QFont cloning 12 | // and updates metrics such as whether the font is monospace 13 | class Font 14 | { 15 | public: 16 | const QFont& font() const { return font_; } 17 | const QRawFont& raw() const { return raw_; } 18 | Font(const QString& family): font_(family), raw_(QRawFont::fromFont(font_)) 19 | { 20 | update(); 21 | } 22 | Font(const QFont& font): font_(font), raw_(QRawFont::fromFont(font_)) 23 | { 24 | update(); 25 | } 26 | Font& operator=(const QFont& f) 27 | { 28 | font_ = f; 29 | raw_ = QRawFont::fromFont(font_); 30 | update(); 31 | return *this; 32 | } 33 | Font& operator=(const QString& family) 34 | { 35 | font_.setFamily(family); 36 | raw_ = QRawFont::fromFont(font_); 37 | update(); 38 | return *this; 39 | } 40 | const QFont& bold_italic_font() const { return bolditalic; } 41 | const QFont& bold_font() const { return bold; } 42 | const QFont& italic_font() const { return italic; } 43 | const QFont& font_for(FontOptions opts) const 44 | { 45 | if (opts & FontOpts::Italic && opts & FontOpts::Bold) 46 | { 47 | return bolditalic; 48 | } 49 | else if (opts & FontOpts::Bold) return bold; 50 | else if (opts & FontOpts::Italic) return italic; 51 | else return font_; 52 | } 53 | bool is_monospace() const { return is_mono; } 54 | private: 55 | void update() 56 | { 57 | QFontMetricsF metrics {font_}; 58 | bolditalic = font_; 59 | bold = font_; 60 | italic = font_; 61 | bolditalic.setBold(true); 62 | bolditalic.setItalic(true); 63 | bold.setBold(true); 64 | italic.setItalic(true); 65 | is_mono = metrics.horizontalAdvance('W') == metrics.horizontalAdvance('a'); 66 | } 67 | QFont font_; 68 | QFont bolditalic; 69 | QFont bold; 70 | QFont italic; 71 | QRawFont raw_; 72 | bool is_mono; 73 | }; 74 | 75 | /// Font dimensions of a monospace font, 76 | /// stores the width and height of a single character. 77 | struct FontDimensions 78 | { 79 | float width; 80 | float height; 81 | }; 82 | #endif // NVUI_FONT_HPP 83 | -------------------------------------------------------------------------------- /src/platform/windows/textformat.cpp: -------------------------------------------------------------------------------- 1 | #include "textformat.hpp" 2 | 3 | using Microsoft::WRL::ComPtr; 4 | 5 | DWRITE_FONT_STYLE dwrite_style(const FontOpts& fo) 6 | { 7 | if (fo & FontOpts::Italic) return DWRITE_FONT_STYLE_ITALIC; 8 | return DWRITE_FONT_STYLE_NORMAL; 9 | } 10 | 11 | DWRITE_FONT_WEIGHT dwrite_weight(const FontOpts& fo) 12 | { 13 | if (fo & FontOpts::Normal) return DWRITE_FONT_WEIGHT_NORMAL; 14 | if (fo & FontOpts::Thin) return DWRITE_FONT_WEIGHT_THIN; 15 | if (fo & FontOpts::Light) return DWRITE_FONT_WEIGHT_LIGHT; 16 | if (fo & FontOpts::Medium) return DWRITE_FONT_WEIGHT_MEDIUM; 17 | if (fo & FontOpts::SemiBold) return DWRITE_FONT_WEIGHT_SEMI_BOLD; 18 | if (fo & FontOpts::Bold) return DWRITE_FONT_WEIGHT_BOLD; 19 | if (fo & FontOpts::ExtraBold) return DWRITE_FONT_WEIGHT_EXTRA_BOLD; 20 | return DWRITE_FONT_WEIGHT_NORMAL; 21 | } 22 | 23 | DWRITE_HIT_TEST_METRICS metrics_for( 24 | std::wstring_view text, 25 | IDWriteFactory* factory, 26 | IDWriteTextFormat* text_format 27 | ) 28 | { 29 | ComPtr tl = nullptr; 30 | factory->CreateTextLayout( 31 | text.data(), 32 | (UINT32) text.size(), 33 | text_format, 34 | std::numeric_limits::max(), 35 | std::numeric_limits::max(), 36 | &tl 37 | ); 38 | DWRITE_HIT_TEST_METRICS ht_metrics; 39 | float ignore; 40 | tl->HitTestTextPosition(0, 0, &ignore, &ignore, &ht_metrics); 41 | return ht_metrics; 42 | } 43 | 44 | TextFormat::TextFormat( 45 | IDWriteFactory* factory, 46 | const std::wstring& name, 47 | float pointsize, 48 | float dpi, 49 | FontOpts default_weight, 50 | FontOpts default_style 51 | ) 52 | { 53 | auto w = dwrite_weight(default_weight); 54 | auto s = dwrite_style(default_style); 55 | // Create reg with default weight 56 | const auto create = [&](auto pptf, auto weight, auto style) { 57 | factory->CreateTextFormat( 58 | name.c_str(), 59 | nullptr, 60 | weight, 61 | style, 62 | DWRITE_FONT_STRETCH_NORMAL, 63 | pointsize * (dpi / 72.0f), 64 | L"en-us", 65 | pptf 66 | ); 67 | }; 68 | create(reg.GetAddressOf(), w, s); 69 | create(bold.GetAddressOf(), DWRITE_FONT_WEIGHT_BOLD, s); 70 | create(italic.GetAddressOf(), w, DWRITE_FONT_STYLE_ITALIC); 71 | create(bolditalic.GetAddressOf(), DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_ITALIC); 72 | auto w_metrics = metrics_for(L"W", factory, reg.Get()); 73 | auto a_metrics = metrics_for(L"a", factory, reg.Get()); 74 | is_mono = w_metrics.width == a_metrics.width; 75 | } 76 | 77 | IDWriteTextFormat* TextFormat::font_for(const FontOptions& fo) const 78 | { 79 | if (fo & FontOpts::Italic && fo & FontOpts::Bold) 80 | { 81 | return bolditalic.Get(); 82 | } 83 | else if (fo & FontOpts::Bold) return bold.Get(); 84 | else if (fo & FontOpts::Italic) return italic.Get(); 85 | else return reg.Get(); 86 | } 87 | 88 | bool TextFormat::is_monospace() const 89 | { 90 | return is_mono; 91 | } 92 | -------------------------------------------------------------------------------- /src/lru.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_LRU_HPP 2 | #define NVUI_LRU_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Default delete does nothing (variables clean up themselves) 10 | template 11 | struct do_nothing_deleter 12 | { 13 | void operator()(T* p) const { Q_UNUSED(p); } 14 | }; 15 | 16 | /// LRUCache with optional custom deleter 17 | /// I use the custom deleter for IDWriteTextLayout1 18 | /// so they get automatically released 19 | /// The caching strategy for QPaintGrid (using QStaticText) 20 | /// cleans up itself so the deleter doesn't need to be specified 21 | /// Once again, using Neovide's idea of caching text blobs. 22 | /// See 23 | /// https://github.com/neovide/neovide/blob/main/src/renderer/fonts/caching_shaper.rs 24 | /// Uses QHash internally since it's expected to be used with QString 25 | /// to cache drawn text. 26 | template> 27 | class LRUCache 28 | { 29 | public: 30 | using key_type = K; 31 | using value_type = V; 32 | LRUCache(std::size_t capacity) 33 | : max_size(capacity), 34 | map(), 35 | keys() 36 | { 37 | map.reserve(static_cast(max_size)); 38 | } 39 | 40 | V& put(K k, V v) 41 | { 42 | V* ptr = nullptr; 43 | auto it = map.find(k); 44 | if (it != map.end()) 45 | { 46 | it->first = std::move(v); 47 | ptr = &it->first; 48 | move_to_front(it->second); 49 | } 50 | else 51 | { 52 | keys.push_front(k); 53 | auto& pair_ref = map[k]; 54 | pair_ref = {v, keys.begin()}; 55 | ptr = &pair_ref.first; 56 | } 57 | if (keys.size() > max_size) 58 | { 59 | const auto& back_key = keys.back(); 60 | auto erase_it = map.find(back_key); 61 | ValueDeleter()(std::addressof(erase_it->first)); 62 | map.erase(erase_it); 63 | keys.pop_back(); 64 | } 65 | return *ptr; 66 | } 67 | 68 | V* get(const K& k) 69 | { 70 | auto it = map.find(k); 71 | if (it != map.end()) 72 | { 73 | move_to_front(it->second); 74 | return std::addressof(it->first); 75 | } 76 | else 77 | { 78 | return nullptr; 79 | } 80 | } 81 | 82 | ~LRUCache() 83 | { 84 | constexpr auto deleter = ValueDeleter(); 85 | for(auto& val : map) deleter(std::addressof(val.first)); 86 | } 87 | 88 | void clear() 89 | { 90 | constexpr auto deleter = ValueDeleter(); 91 | for(auto& val : map) deleter(std::addressof(val.first)); 92 | map.clear(); 93 | keys.clear(); 94 | } 95 | 96 | private: 97 | // https://stackoverflow.com/questions/14579957/std-container-c-move-to-front 98 | void move_to_front(typename std::list::iterator it) 99 | { 100 | if (it != keys.begin()) keys.splice(keys.begin(), keys, it); 101 | } 102 | std::size_t max_size = 10; 103 | QHash::iterator>> map; 104 | std::list keys; 105 | }; 106 | 107 | #endif // NVUI_LRU_HPP 108 | -------------------------------------------------------------------------------- /src/platform/windows/d2deditor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_D2DEDITOR_HPP 2 | #define NVUI_D2DEDITOR_HPP 3 | 4 | #include "qt_editorui_base.hpp" 5 | #include "textformat.hpp" 6 | 7 | #ifndef Q_OS_WIN 8 | #error "D2DEditor requires Windows." 9 | #endif // Q_OS_WIN 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class D2DEditor : public QWidget, public QtEditorUIBase 18 | { 19 | Q_OBJECT 20 | using Base = QtEditorUIBase; 21 | template 22 | using ComPtr = Microsoft::WRL::ComPtr; 23 | // Same structure as 'Font' in font.hpp in terms of storing fonts 24 | // but for IDWriteTextFormat 25 | public: 26 | D2DEditor( 27 | int cols, 28 | int rows, 29 | std::unordered_map capabilities, 30 | std::string nvim_path, 31 | std::vector nvim_args, 32 | QWidget* parent = nullptr, 33 | bool vsync = true 34 | ); 35 | void setup() override; 36 | QPaintEngine* paintEngine() const override { return nullptr; } 37 | u32 font_for_ucs(u32 ucs); 38 | // Creates a bitmap render target with the given width and height. 39 | struct OffscreenRenderingPair 40 | { 41 | ComPtr target; 42 | ComPtr bitmap; 43 | }; 44 | OffscreenRenderingPair create_render_target(u32 width, u32 height); 45 | IDWriteFactory* dwrite_factory(); 46 | const auto& fallback_list() const { return dw_formats; } 47 | ~D2DEditor() override; 48 | signals: 49 | void layouts_invalidated(); 50 | void render_targets_updated(); 51 | protected: 52 | void linespace_changed(float new_ls) override; 53 | void charspace_changed(float new_cs) override; 54 | protected: 55 | void resizeEvent(QResizeEvent* event) override; 56 | void mousePressEvent(QMouseEvent* event) override; 57 | void mouseReleaseEvent(QMouseEvent* event) override; 58 | void wheelEvent(QWheelEvent* event) override; 59 | void dropEvent(QDropEvent* event) override; 60 | void dragEnterEvent(QDragEnterEvent* event) override; 61 | void inputMethodEvent(QInputMethodEvent* event) override; 62 | void mouseMoveEvent(QMouseEvent* event) override; 63 | void paintEvent(QPaintEvent* event) override; 64 | void keyPressEvent(QKeyEvent* event) override; 65 | bool focusNextPrevChild(bool) override { return false; } 66 | void focusInEvent(QFocusEvent* event) override; 67 | void focusOutEvent(QFocusEvent* event) override; 68 | private: 69 | void set_vsync(bool); 70 | void update_font_metrics(); 71 | std::unique_ptr popup_new() override; 72 | std::unique_ptr cmdline_new() override; 73 | void redraw() override; 74 | void create_grid(u32 x, u32 y, u32 w, u32 h, u64 id) override; 75 | void set_fonts(std::span fonts) override; 76 | u32 calc_fallback_index(u32 ucs); 77 | private: 78 | std::unordered_map fallback_indices {}; 79 | std::vector> dw_fonts; 80 | std::vector dw_formats; 81 | ComPtr dw_factory = nullptr; 82 | ComPtr d2d_factory = nullptr; 83 | ComPtr device_context = nullptr; 84 | ComPtr hwnd_target = nullptr; 85 | ComPtr device = nullptr; 86 | float current_point_size = 12.0f; 87 | bool vsync = true; 88 | }; 89 | 90 | #endif // NVUI_D2DEDITOR_HPP 91 | -------------------------------------------------------------------------------- /src/nvim_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_NVIM_UTILS_HPP 2 | #define NVUI_NVIM_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include "object.hpp" 7 | #include "nvim.hpp" 8 | 9 | /** 10 | * Automatically unpack ObjectArray into the desired parameters, 11 | * or exit if it doesn't match. 12 | */ 13 | template 14 | std::function paramify(Func&& f) 15 | { 16 | return [f](const ObjectArray& arg_list) { 17 | std::tuple t; 18 | constexpr std::size_t types_len = sizeof...(T); 19 | if (arg_list.size() < types_len) return; 20 | bool valid = true; 21 | std::size_t idx = 0; 22 | for_each_in_tuple(t, [&](auto& p) { 23 | using val_type = std::remove_reference_t; 24 | if constexpr(std::is_same_v) 25 | { 26 | std::optional s {}; 27 | if (auto* o_s = arg_list.at(idx).string()) 28 | { 29 | s = QString::fromStdString(*o_s); 30 | } 31 | if (!s) { valid = false; ++idx; return; } 32 | else p = std::move(s.value()); 33 | ++idx; 34 | return; 35 | } 36 | std::optional v = arg_list.at(idx).try_convert(); 37 | if (!v) { valid = false; return; } 38 | p = std::move(v.value()); 39 | ++idx; 40 | }); 41 | if (!valid) return; 42 | std::apply(f, t); 43 | }; 44 | } 45 | 46 | // Run a function when the given Nvim object receieves a notification 47 | // with name 'method_name'. 48 | inline void listen_for_notification( 49 | Nvim& nvim, 50 | std::string method, 51 | std::function func, 52 | QObject* target 53 | ) 54 | { 55 | nvim.set_notification_handler( 56 | std::move(method), 57 | [target, cb = std::move(func)](Object obj) { 58 | QMetaObject::invokeMethod( 59 | target, 60 | [o = std::move(obj), f = std::move(cb)] { 61 | auto* arr = o.array(); 62 | if (!(arr && arr->size() >= 3)) return; 63 | auto* params = arr->at(2).array(); 64 | if (!params) return; 65 | f(std::move(*params)); 66 | }, 67 | Qt::QueuedConnection 68 | ); 69 | } 70 | ); 71 | } 72 | 73 | template 74 | void handle_request( 75 | Nvim& nvim, 76 | const std::string& method, 77 | std::function< 78 | std::tuple, std::optional> (const ObjectArray&) 79 | > func, 80 | QObject* target 81 | ) 82 | { 83 | nvim.set_request_handler( 84 | method, 85 | [target, cb = std::move(func), &nvim](Object obj) { 86 | QMetaObject::invokeMethod( 87 | target, 88 | [o = std::move(obj), f = std::move(cb), &nvim] { 89 | auto* arr = o.array(); 90 | if (!arr || arr->size() < 4) return; 91 | auto msgid = arr->at(1).u64(); 92 | auto params = arr->at(3).array(); 93 | if (!msgid || !params) return; 94 | std::optional res; 95 | std::optional err; 96 | std::tie(res, err) = f(*arr); 97 | if (res) 98 | { 99 | nvim.send_response(*msgid, *res, msgpack::object()); 100 | } 101 | else 102 | { 103 | nvim.send_response(*msgid, msgpack::object(), *err); 104 | } 105 | } 106 | ); 107 | }); 108 | } 109 | 110 | #endif // NVUI_NVIM_UTILS_HPP 111 | -------------------------------------------------------------------------------- /vim/doc/tags: -------------------------------------------------------------------------------- 1 | :NvuiAnimationsEnabled nvui.txt /*:NvuiAnimationsEnabled* 2 | :NvuiCaretExtendBottom nvui.txt /*:NvuiCaretExtendBottom* 3 | :NvuiCaretExtendTop nvui.txt /*:NvuiCaretExtendTop* 4 | :NvuiCharspace nvui.txt /*:NvuiCharspace* 5 | :NvuiCmdBigFontScaleFactor nvui.txt /*:NvuiCmdBigFontScaleFactor* 6 | :NvuiCmdCenterXPos nvui.txt /*:NvuiCmdCenterXPos* 7 | :NvuiCmdCenterYPos nvui.txt /*:NvuiCmdCenterYPos* 8 | :NvuiCmdFontFamily nvui.txt /*:NvuiCmdFontFamily* 9 | :NvuiCmdFontSize nvui.txt /*:NvuiCmdFontSize* 10 | :NvuiCmdHeight nvui.txt /*:NvuiCmdHeight* 11 | :NvuiCmdLeftPos nvui.txt /*:NvuiCmdLeftPos* 12 | :NvuiCmdPadding nvui.txt /*:NvuiCmdPadding* 13 | :NvuiCmdTopPos nvui.txt /*:NvuiCmdTopPos* 14 | :NvuiCmdWidth nvui.txt /*:NvuiCmdWidth* 15 | :NvuiCmdline nvui.txt /*:NvuiCmdline* 16 | :NvuiCursorAnimationDuration nvui.txt /*:NvuiCursorAnimationDuration* 17 | :NvuiCursorFrametime nvui.txt /*:NvuiCursorFrametime* 18 | :NvuiCursorHideWhileTyping nvui.txt /*:NvuiCursorHideWhileTyping* 19 | :NvuiCursorScaler nvui.txt /*:NvuiCursorScaler* 20 | :NvuiEditorNext nvui.txt /*:NvuiEditorNext* 21 | :NvuiEditorPrev nvui.txt /*:NvuiEditorPrev* 22 | :NvuiEditorSelect nvui.txt /*:NvuiEditorSelect* 23 | :NvuiEditorSpawn nvui.txt /*:NvuiEditorSpawn* 24 | :NvuiEditorSwitch nvui.txt /*:NvuiEditorSwitch* 25 | :NvuiFrameless nvui.txt /*:NvuiFrameless* 26 | :NvuiFullscreen nvui.txt /*:NvuiFullscreen* 27 | :NvuiIMEDisable nvui.txt /*:NvuiIMEDisable* 28 | :NvuiIMEEnable nvui.txt /*:NvuiIMEEnable* 29 | :NvuiIMEToggle nvui.txt /*:NvuiIMEToggle* 30 | :NvuiMoveAnimationDuration nvui.txt /*:NvuiMoveAnimationDuration* 31 | :NvuiMoveAnimationFrametime nvui.txt /*:NvuiMoveAnimationFrametime* 32 | :NvuiMoveScaler nvui.txt /*:NvuiMoveScaler* 33 | :NvuiOpacity nvui.txt /*:NvuiOpacity* 34 | :NvuiPopupMenu nvui.txt /*:NvuiPopupMenu* 35 | :NvuiPopupMenuBorderColor nvui.txt /*:NvuiPopupMenuBorderColor* 36 | :NvuiPopupMenuBorderWidth nvui.txt /*:NvuiPopupMenuBorderWidth* 37 | :NvuiPopupMenuDefaultIconBg nvui.txt /*:NvuiPopupMenuDefaultIconBg* 38 | :NvuiPopupMenuDefaultIconFg nvui.txt /*:NvuiPopupMenuDefaultIconFg* 39 | :NvuiPopupMenuIconBg nvui.txt /*:NvuiPopupMenuIconBg* 40 | :NvuiPopupMenuIconFg nvui.txt /*:NvuiPopupMenuIconFg* 41 | :NvuiPopupMenuIconFgBg nvui.txt /*:NvuiPopupMenuIconFgBg* 42 | :NvuiPopupMenuIconOffset nvui.txt /*:NvuiPopupMenuIconOffset* 43 | :NvuiPopupMenuIconsRightAlign nvui.txt /*:NvuiPopupMenuIconsRightAlign* 44 | :NvuiPopupMenuIconsToggle nvui.txt /*:NvuiPopupMenuIconsToggle* 45 | :NvuiPopupMenuInfoColumns nvui.txt /*:NvuiPopupMenuInfoColumns* 46 | :NvuiPopupMenuMaxChars nvui.txt /*:NvuiPopupMenuMaxChars* 47 | :NvuiPopupMenuMaxItems nvui.txt /*:NvuiPopupMenuMaxItems* 48 | :NvuiScrollAnimationDuration nvui.txt /*:NvuiScrollAnimationDuration* 49 | :NvuiScrollFrametime nvui.txt /*:NvuiScrollFrametime* 50 | :NvuiScrollScaler nvui.txt /*:NvuiScrollScaler* 51 | :NvuiSecondsBeforeIdle nvui.txt /*:NvuiSecondsBeforeIdle* 52 | :NvuiSnapshotLimit nvui.txt /*:NvuiSnapshotLimit* 53 | :NvuiTitlebarBg nvui.txt /*:NvuiTitlebarBg* 54 | :NvuiTitlebarColors nvui.txt /*:NvuiTitlebarColors* 55 | :NvuiTitlebarFg nvui.txt /*:NvuiTitlebarFg* 56 | :NvuiTitlebarFontFamily nvui.txt /*:NvuiTitlebarFontFamily* 57 | :NvuiTitlebarFontSize nvui.txt /*:NvuiTitlebarFontSize* 58 | :NvuiTitlebarSeparator nvui.txt /*:NvuiTitlebarSeparator* 59 | :NvuiTitlebarUnsetColors nvui.txt /*:NvuiTitlebarUnsetColors* 60 | :NvuiToggleFrameless nvui.txt /*:NvuiToggleFrameless* 61 | :NvuiToggleFullscreen nvui.txt /*:NvuiToggleFullscreen* 62 | :nvui-instances nvui.txt /*:nvui-instances* 63 | nvui-cmdline nvui.txt /*nvui-cmdline* 64 | nvui-cursor nvui.txt /*nvui-cursor* 65 | nvui-general nvui.txt /*nvui-general* 66 | nvui-ime nvui.txt /*nvui-ime* 67 | nvui-multigrid nvui.txt /*nvui-multigrid* 68 | nvui-popup nvui.txt /*nvui-popup* 69 | nvui-titlebar nvui.txt /*nvui-titlebar* 70 | nvui-toc nvui.txt /*nvui-toc* 71 | nvui.txt nvui.txt /*nvui.txt* 72 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | ## BUILDING 2 |
3 | You Need: 4 |
    5 |
  • C++ Compiler that supports C++17
  • 6 |
  • CMake
  • 7 |
  • On Linux, see below for the dependencies needed
  • 8 |
9 | 10 | For package management, I use vcpkg @ 807a79876 as a submodule. 11 | See the vcpkg.json for the packages used in this project. 12 | 13 | ## Installling Dependencies 14 |
15 | 16 | ### Linux 17 | On Linux, dependencies installed using vcpkg require some packages to be 18 | installed by the system package manger. In order for the dependencies 19 | to build properly you must install the follwing dependencies 20 | 21 | (apt) 22 | sudo apt-get install -y gperf autoconf build-essential libtool 23 | libgl1-mesa-dev libxi-dev libx11-dev libxext-dev 24 | libxkbcommon-x11-dev libglu1-mesa-dev libx11-xcb-dev 25 | '^libxcb.*-dev' libxrender-dev ninja-build curl 26 | zip unzip tar autopoint python 27 | 28 | 29 | I don't have experience with any other package manger, but these are all the required packages on Ubuntu. This is also what the Github Actions Ubuntu image does. 30 | 31 | #### Arch Linux 32 | 33 | - Build without vcpkg 34 | 35 | 1. Dependencies 36 | 37 | ```bash 38 | sudo pacman -Sy 39 | sudo pacman -S clang make boost fmt hicolor-icon-theme msgpack-cxx qt5-base qt5-svg catch2 cmake ninja git curl wget --needed 40 | ``` 41 | 42 | 2. Clone 43 | 44 | ```bash 45 | git clone https://github.com/rohit-px2/nvui.git --recurse-submodules 46 | cd nvui 47 | ``` 48 | 49 | 3. Build 50 | 51 | ```bash 52 | cmake -B build . -DCMAKE_BUILD_TYPE=Release 53 | cmake --build build --target nvui --config Release 54 | ``` 55 | 56 | 4. Install 57 | 58 | Nvui need to read the asserts and vim directories at parent directory. 59 | So I make a script to call it, not put it directly in the /bin. 60 | 61 | Also the path `~/.local` can be replace with `/usr/local` to use nvui 62 | system wide. 63 | 64 | ```bash 65 | mkdir -p ~/.local/share/nvui/bin 66 | mkdir -p ~/.local/bin 67 | cp ./build/nvui ~/.local/share/nvui/bin 68 | cp -r ./vim ~/.local/share/nvui/vim 69 | cp -r ./assets ~/.local/share/nvui/assets 70 | echo -e '#!/bin/bash\n\n$HOME/.local/share/nvui/bin/nvui --detached -- "$@"' > ~/.local/bin/nvui 71 | chmod +x ~/.local/bin/nvui 72 | ``` 73 | 74 | ### Windows 75 | On Windows you don't need to install any dependencies, vcpkg takes care of it. 76 |
77 | 78 | ### Building 79 | 80 | cmake -B . -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release 81 | cmake --build --target nvui --config Release 82 | 83 | ### NOTE (Windows, MSVC): 84 | When you use MSVC / Visual Studio the project is not built into the "build" folder, 85 | instead it is built into "build/Release". 86 | If you run the executable from this directory, nvui will fail to find the assets and vim files, and, if you have commands in your vimrc, they won't be recognized (since nvui.vim is not loaded). Also, you won't see any popup menu icons (if you use the external popup menu). 87 | You should move the executable and DLLs from the "build/Release" folder into the "build" folder. 88 | This issue does not occur with Clang, since the executable is placed right into 89 | the "build" directory. 90 |
91 | 92 | ## Packaging Executable 93 | nvui must find the assets folder in "../assets" and the folder for vim files at "../vim". Thus, if you are trying to package the executable the folder everything is packaged in should look like this: 94 | 95 | - nvui (the main folder) 96 | - bin (can be whatever name, this is where the executable is stored) 97 | - nvui executable 98 | - (On Windows) the "plugins" directory, DLLs 99 | - assets 100 | - vim 101 | 102 | This is what the "package" scripts do (see the "scripts" folder). 103 |
104 | -------------------------------------------------------------------------------- /src/qpaintgrid.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_QPAINTGRID_HPP 2 | #define NVUI_QPAINTGRID_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "cursor.hpp" 9 | #include "grid.hpp" 10 | #include "hlstate.hpp" 11 | #include "lru.hpp" 12 | 13 | class QEditor; 14 | 15 | /// A class that implements rendering for a grid using Qt's 16 | /// QPainter. The QPaintGrid class draws to a QPixmap and 17 | /// works with the EditorArea class to draw these pixmaps to 18 | /// the screen. 19 | class QPaintGrid : public GridBase 20 | { 21 | Q_OBJECT 22 | using GridBase::u16; 23 | struct Snapshot 24 | { 25 | Viewport vp; 26 | QPixmap image; 27 | }; 28 | public: 29 | template 30 | QPaintGrid(QEditor* ea, GridBaseArgs... args) 31 | : GridBase(args...), 32 | editor_area(ea), 33 | pixmap(), 34 | top_left(), 35 | text_cache(2000) 36 | { 37 | update_pixmap_size(); 38 | update_position(x, y); 39 | initialize_cache(); 40 | initialize_scroll_animation(); 41 | initialize_move_animation(); 42 | } 43 | ~QPaintGrid() override = default; 44 | void set_size(u16 w, u16 h) override; 45 | void set_pos(double new_x, double new_y) override; 46 | void viewport_changed(Viewport vp) override; 47 | /// Process the draw commands in the event queue 48 | void process_events(); 49 | /// Returns the grid's paint buffer (QPixmap) 50 | const QPixmap& buffer() const { return pixmap; } 51 | /// The top-left corner of the grid (where to start drawing the buffer). 52 | QPointF pos() const { return top_left; } 53 | /// Renders to the painter. 54 | void render(QPainter& painter); 55 | /// Draws the cursor on the painter, relative to the grid's 56 | /// current position (see pos()) 57 | void draw_cursor(QPainter& painter, const Cursor& cursor); 58 | private: 59 | /// Draw the grid range given by the rect. 60 | void draw(QPainter& p, QRect r, const double font_offset); 61 | /// Draw the given text with attr and def_clrs indicating 62 | /// the background, foreground colors and font options. 63 | void draw_text_and_bg( 64 | QPainter& painter, 65 | const QString& text, 66 | const HLAttr& attr, 67 | const HLAttr& def_clrs, 68 | const QPointF& start, 69 | const QPointF& end, 70 | const int offset, 71 | const QFont& font, 72 | float font_width, 73 | float font_height 74 | ); 75 | void draw_text( 76 | QPainter& painter, 77 | const QString& text, 78 | const Color& fg, 79 | const std::optional& sp, 80 | const QRectF& rect, 81 | const FontOptions font_opts, 82 | const QFont& font, 83 | float font_width, 84 | float font_height 85 | ); 86 | /// Update the pixmap size 87 | void update_pixmap_size(); 88 | /// Initialize the cache 89 | void initialize_cache(); 90 | /// Initialize scroll animation timer 91 | void initialize_scroll_animation(); 92 | /// Initialize move animation timer 93 | void initialize_move_animation(); 94 | /// Update the grid's position (new position can be found through pos()). 95 | void update_position(double new_x, double new_y); 96 | private: 97 | std::vector snapshots; 98 | /// Links up with the default Qt rendering 99 | QEditor* editor_area; 100 | QPixmap pixmap; 101 | QTimer move_update_timer {}; 102 | float move_animation_time = -1.f; 103 | QPointF top_left; 104 | float start_scroll_y = 0.f; 105 | float current_scroll_y = 0.f; 106 | float cur_left = 0.f; 107 | float cur_top = 0.f; 108 | bool is_scrolling = false; 109 | float scroll_animation_time; 110 | QTimer scroll_animation_timer {}; 111 | float dest_move_x = 0.f; 112 | float dest_move_y = 0.f; 113 | float old_move_x = 0.f; 114 | float old_move_y = 0.f; 115 | float destination_scroll_y = 0.f; 116 | using FontOptions = decltype(HLAttr::font_opts); 117 | LRUCache, QStaticText> text_cache; 118 | }; 119 | 120 | QFont::Weight qfont_weight(const FontOpts& fo); 121 | QFont::Style qfont_style(const FontOpts& fo); 122 | 123 | #endif // NVUI_QPAINTGRID_HPP 124 | -------------------------------------------------------------------------------- /src/cmdline.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_CMDLINE_HPP 2 | #define NVUI_CMDLINE_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "hlstate.hpp" 10 | #include "cursor.hpp" 11 | #include "types.hpp" 12 | 13 | class Nvim; 14 | 15 | struct Cmdline 16 | { 17 | Cmdline(const HLState& hl_state, const Cursor* cursor); 18 | virtual ~Cmdline() = default; 19 | virtual void register_nvim(Nvim&) = 0; 20 | void cmdline_show(std::span objs); 21 | void cmdline_hide(std::span objs); 22 | void cmdline_cursor_pos(std::span objs); 23 | void cmdline_special_char(std::span objs); 24 | void cmdline_block_show(std::span objs); 25 | void cmdline_block_append(std::span objs); 26 | void cmdline_block_hide(std::span objs); 27 | void set_fg(Color); 28 | void set_bg(Color); 29 | void set_x(float left); 30 | void set_y(float top); 31 | void set_center_x(float x); 32 | void set_center_y(float y); 33 | void set_width(float width); 34 | void set_height(float height); 35 | void set_padding(u32 pad); 36 | bool hidden() const; 37 | virtual void editor_resized(int width, int height) = 0; 38 | virtual QRect get_rect() const = 0; 39 | virtual void set_font_family(std::string_view family) = 0; 40 | virtual void set_font_size(double point_size) = 0; 41 | void set_border_width(int pixels); 42 | void set_border_color(Color color); 43 | QString get_content_string() const; 44 | protected: 45 | struct Chunk 46 | { 47 | int attr; 48 | QString text; 49 | }; 50 | using Content = std::vector; 51 | const HLState& hl_state; 52 | const Cursor* p_cursor; 53 | std::optional inner_fg; 54 | std::optional inner_bg; 55 | std::optional centered_x; 56 | std::optional centered_y; 57 | Content content; 58 | std::vector block; 59 | float border_width = 1.f; 60 | Color border_color {0}; 61 | std::optional first_char; 62 | // Rectangle relative to screen size of editor area. 63 | // Indicates (x, y, w, h). Height is ignored through since 64 | // the cmdline should adjust automatically. 65 | // Default puts the cmdline centered at the top of the window, 66 | // and taking up half the height of the editor area. 67 | QRectF rel_rect {0.25, 0, 0.5, 0.10}; 68 | u32 padding = 1; 69 | // Before which character the cursor will show up on the current line. 70 | int cursor_pos = 0; 71 | bool is_hidden = true; 72 | int indent = 0; 73 | private: 74 | QString complete_content_string; 75 | protected: 76 | virtual void colors_changed(Color fg, Color bg) = 0; 77 | virtual void redraw() = 0; 78 | virtual void do_hide() = 0; 79 | // do_show() does not signal a redraw, it just says to make 80 | // the current content visible. 81 | // redraw() will be called before do_show() 82 | // when the cmdline is updated. 83 | virtual void do_show() = 0; 84 | virtual void border_changed() = 0; 85 | virtual void rect_changed(QRectF relative_rect) = 0; 86 | private: 87 | void update_content_string(); 88 | void convert_content(Content&, const ObjectArray&); 89 | }; 90 | 91 | class CmdlineQ : public Cmdline, public QWidget 92 | { 93 | public: 94 | CmdlineQ(const HLState& hls, const Cursor* crs, QWidget* parent = nullptr); 95 | void register_nvim(Nvim&) override; 96 | QRect get_rect() const override; 97 | ~CmdlineQ() override; 98 | protected: 99 | void do_hide() override; 100 | void do_show() override; 101 | void redraw() override; 102 | void colors_changed(Color fg, Color bg) override; 103 | void set_font_family(std::string_view family) override; 104 | void set_font_size(double point_size) override; 105 | void border_changed() override; 106 | void rect_changed(QRectF relative_rect) override; 107 | void paintEvent(QPaintEvent*) override; 108 | private: 109 | void editor_resized(int width, int height) override; 110 | int num_lines(const Content&, const QFontMetricsF&) const; 111 | int fitting_height() const; 112 | void draw_cursor(QPainter&, const Cursor&); 113 | QFont cmd_font; 114 | }; 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/titlebar.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_TITLEBAR_HPP 2 | #define NVUI_TITLEBAR_HPP 3 | 4 | #include "constants.hpp" 5 | #include "utils.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /// MenuButtons for our TitleBar that inherit from QPushButton 21 | /// These have overridden the default hover events to provide 22 | /// support for custom resizing as well as background changing 23 | /// on hover. For more info, see the implementation 24 | class MenuButton; 25 | 26 | class Window; 27 | 28 | /// Custom titlebar implementation that operates on the Window class. 29 | class TitleBar : public QWidget 30 | { 31 | Q_OBJECT 32 | public: 33 | TitleBar(QString text, Window* window); 34 | /** 35 | * Sets the title bar text. 36 | */ 37 | void set_title_text(const QString& text); 38 | /** 39 | * Sets foreground to color if foreground is true, 40 | * otherwise sets background to color. 41 | */ 42 | void set_color(QColor color, bool is_foreground = true); 43 | /** 44 | * Sets the foreground and background color to bg and fg respectively. 45 | */ 46 | void set_color(QColor fg, QColor bg); 47 | /** 48 | * Update the icons based on the status of the attached window (win). 49 | */ 50 | void update_maxicon(); 51 | /** 52 | * Hide the titlebar 53 | */ 54 | inline void hide() 55 | { 56 | if (titlebar_widget->isHidden()) return; 57 | titlebar_widget->hide(); 58 | } 59 | /** 60 | * Show the titlebar 61 | */ 62 | inline void show() 63 | { 64 | if (titlebar_widget->isVisible()) return; 65 | titlebar_widget->show(); 66 | } 67 | inline void set_font_family(QString new_fam) 68 | { 69 | title_font.setFamily(new_fam); 70 | label->setFont(title_font); 71 | } 72 | inline void set_font_size(double new_size) 73 | { 74 | title_font.setPointSizeF(new_size); 75 | label->setFont(title_font); 76 | } 77 | inline QWidget* widget() const 78 | { 79 | return titlebar_widget; 80 | } 81 | private: 82 | QIcon icon_or( 83 | const QString& fp, 84 | QStyle::StandardPixmap sp 85 | ) const 86 | { 87 | auto icon = icon_from_svg(fp, foreground); 88 | if (!icon) 89 | { 90 | auto sp_px = QApplication::style()->standardPixmap(sp); 91 | auto mask = sp_px.createMaskFromColor({"black"}, Qt::MaskOutColor); 92 | sp_px.fill(foreground); 93 | sp_px.setMask(mask); 94 | icon = QIcon(sp_px); 95 | } 96 | return *icon; 97 | } 98 | QIcon get_close_icon() const 99 | { 100 | return icon_or(constants::closeicon(), QStyle::SP_TitleBarCloseButton); 101 | } 102 | QIcon get_maximized_icon() const 103 | { 104 | return icon_or( 105 | constants::maxicon_second(), 106 | QStyle::SP_TitleBarNormalButton 107 | ); 108 | } 109 | QIcon get_maximize_icon() const 110 | { 111 | return icon_or(constants::maxicon(), QStyle::SP_TitleBarMaxButton); 112 | } 113 | QIcon get_minimize_icon() const 114 | { 115 | return icon_or(constants::minicon(), QStyle::SP_TitleBarMinButton); 116 | } 117 | QIcon get_app_icon() const 118 | { 119 | return icon_from_svg(constants::appicon(), foreground).value_or(QIcon()); 120 | } 121 | /** 122 | * Updates the titlebar with new colors. 123 | * This should only be called after the colors are updated 124 | (not when new text is set). 125 | */ 126 | void update_titlebar(); 127 | bool maximized; 128 | QColor foreground; 129 | QColor background; 130 | QIcon close_icon; 131 | QIcon max_icon; 132 | QIcon min_icon; 133 | MenuButton* close_btn; 134 | MenuButton* max_btn; 135 | MenuButton* min_btn; 136 | //QPushButton* close_btn; 137 | //QPushButton* max_btn; 138 | //QPushButton* min_btn; 139 | Window* win; 140 | QFont title_font; 141 | QLabel* label; 142 | QHBoxLayout* layout; 143 | QWidget* titlebar_widget; 144 | public slots: 145 | void minimize_maximize(); 146 | void colors_changed(QColor fg, QColor bg); 147 | void win_state_changed(Qt::WindowStates state); 148 | signals: 149 | void resize_move(QPointF p); 150 | }; 151 | #endif 152 | -------------------------------------------------------------------------------- /test/test_object.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "object.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | msgpack::object_handle pack_unpack(const T& t) 9 | { 10 | std::stringstream ss; 11 | msgpack::pack(ss, t); 12 | const auto& s = ss.str(); 13 | return msgpack::unpack(s.data(), s.size()); 14 | } 15 | 16 | TEST_CASE("Object parsing works", "[Object]") 17 | { 18 | const auto test_primitive = [&](auto prim, auto type) { 19 | auto o = msgpack::object(prim); 20 | REQUIRE(o.type == type); 21 | auto parsed = Object::parse(o); 22 | using prim_type = std::remove_reference_t; 23 | REQUIRE(parsed.has()); 24 | REQUIRE(parsed.get() == prim); 25 | }; 26 | SECTION("Primitives") 27 | { 28 | test_primitive(uint64_t(5), msgpack::type::POSITIVE_INTEGER); 29 | test_primitive(int64_t(-1), msgpack::type::NEGATIVE_INTEGER); 30 | test_primitive(false, msgpack::type::BOOLEAN); 31 | test_primitive(true, msgpack::type::BOOLEAN); 32 | test_primitive(double(42.3), msgpack::type::FLOAT64); 33 | std::string s = "hello"; 34 | msgpack::object_handle oh = pack_unpack(s); 35 | const auto& o = oh.get(); 36 | auto parsed = Object::parse(o); 37 | REQUIRE(parsed.has()); 38 | REQUIRE(parsed.get() == s.c_str()); 39 | } 40 | SECTION("Arrays") 41 | { 42 | { 43 | std::vector v {-1, -2, -3, -4}; 44 | auto oh = pack_unpack(v); 45 | const auto& o = oh.get(); 46 | REQUIRE(o.type == msgpack::type::ARRAY); 47 | REQUIRE(o.via.array.size == v.size()); 48 | auto parsed = Object::parse(o); 49 | REQUIRE(parsed.has()); 50 | const auto& omap = parsed.get(); 51 | for(std::size_t i = 0; i < omap.size(); ++i) 52 | { 53 | using val_type = decltype(v)::value_type; 54 | REQUIRE(omap.at(i).has()); 55 | REQUIRE(omap.at(i).get() == v.at(i)); 56 | } 57 | } 58 | { 59 | std::vector v {"hello", "hi"}; 60 | std::stringstream ss; 61 | msgpack::pack(ss, v); 62 | auto oh = msgpack::unpack(ss.str().data(), ss.str().size()); 63 | const auto& o = oh.get(); 64 | REQUIRE(o.type == msgpack::type::ARRAY); 65 | REQUIRE(o.via.array.size == v.size()); 66 | auto parsed = Object::parse(o); 67 | REQUIRE(parsed.has()); 68 | const auto& omap = parsed.get(); 69 | for(std::size_t i = 0; i < omap.size(); ++i) 70 | { 71 | using val_type = std::string; 72 | REQUIRE(omap.at(i).has()); 73 | REQUIRE(omap.at(i).get() == v.at(i)); 74 | } 75 | } 76 | } 77 | SECTION("Maps") 78 | { 79 | using string_map = std::unordered_map; 80 | string_map mp { 81 | {"hello", "hi"}, 82 | {"here", "there"} 83 | }; 84 | std::stringstream ss; 85 | msgpack::pack(ss, mp); 86 | auto oh = msgpack::unpack(ss.str().data(), ss.str().size()); 87 | const auto& o = oh.get(); 88 | REQUIRE(o.type == msgpack::type::MAP); 89 | REQUIRE(o.via.map.size == static_cast(mp.size())); 90 | auto parsed = Object::parse(o); 91 | REQUIRE(parsed.has()); 92 | const auto& omap = parsed.get(); 93 | REQUIRE(omap.size() == static_cast(mp.size())); 94 | for(const auto& [key, val] : omap) 95 | { 96 | REQUIRE(mp.contains(key)); 97 | REQUIRE(val.has()); 98 | } 99 | } 100 | } 101 | 102 | TEST_CASE("Extracting types from Object") 103 | { 104 | SECTION("Implicit integer conversion") 105 | { 106 | Object o = std::uint64_t(5); 107 | REQUIRE(o.has()); 108 | // Should be able to convert to int(5) 109 | try 110 | { 111 | int x = (int) o; 112 | REQUIRE(x == 5); 113 | } 114 | catch(...) { REQUIRE(!"Could not convert o = 5 to int."); } 115 | } 116 | SECTION("Get throws exception if it doesn't work") 117 | { 118 | Object o = std::uint64_t(5); 119 | bool caught = false; 120 | try 121 | { 122 | std::string s = (std::string) o; 123 | REQUIRE(false); 124 | } 125 | catch(...) { caught = true; } 126 | REQUIRE(caught); 127 | } 128 | SECTION("Optional function works") 129 | { 130 | Object o = std::string("hello world"); 131 | auto str_opt = o.try_convert(); 132 | REQUIRE(str_opt); 133 | REQUIRE(*str_opt == "hello world"); 134 | } 135 | SECTION("Optional function returns nullopt if could not convert") 136 | { 137 | Object o = std::uint64_t(5); 138 | // Optional returns nullopt if not convertible 139 | auto str_opt = o.try_convert(); 140 | REQUIRE(!str_opt); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | #if(DEFINED ENV{QT_DIR}) 3 | #message("Qt Dir is Set") 4 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_DIR}") 5 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_DIR}/Qt5Core") 6 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_DIR}/Qt5Widgets") 7 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_DIR}/Qt5Gui") 8 | #else() 9 | #message("Could not find QT_DIR") 10 | #endif() 11 | #if (DEFINED ENV{QT_SVGDIR}) 12 | #message("Qt SVG Dir is set") 13 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_SVGDIR}") 14 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_SVGDIR}/Qt5Svg") 15 | #list(APPEND CMAKE_PREFIX_PATH "$ENV{QT_SVGDIR}/Qt5Gui") 16 | #endif() 17 | option(BUILD_TESTS "Build tests" OFF) 18 | if (BUILD_TESTS) 19 | list(APPEND VCPKG_MANIFEST_FEATURES "tests") 20 | endif() 21 | 22 | if(NOT CMAKE_BUILD_TYPE) 23 | set(CMAKE_BUILD_TYPE Debug) 24 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") 25 | endif() 26 | project(nvui) 27 | if(MSVC) 28 | #C5054 - Qt 29 | #C4702 - Msgpack 30 | #C4127 - Qt 31 | #C4458 - fmt 32 | add_compile_options(/W4 /WX /wd5054 /wd4459 /wd4458 /wd4244 /wd4127 /wd4702) 33 | set(CMAKE_CXX_FLAGS_RELEASE /O2 /DNDEBUG) 34 | else() 35 | add_compile_options(-Wall -Wextra -pedantic -Werror -Wfatal-errors -Wno-language-extension-token -Wno-unknown-pragmas) 36 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG") 37 | endif() 38 | set(CMAKE_CXX_STANDARD 20) 39 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 40 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 41 | find_package(msgpack CONFIG REQUIRED) 42 | find_package(Qt5 5.15.2 REQUIRED COMPONENTS Core Gui Svg Widgets) 43 | find_package(fmt CONFIG REQUIRED) 44 | set(CMAKE_AUTOMOC ON) 45 | set(CMAKE_AUTORCC ON) 46 | set(CMAKE_AUTOUIC ON) 47 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 48 | file(GLOB SOURCES 49 | src/titlebar.hpp 50 | src/titlebar.cpp 51 | src/utils.cpp 52 | src/nvim.hpp 53 | src/nvim.cpp 54 | src/hlstate.hpp 55 | src/hlstate.cpp 56 | src/window.hpp 57 | src/window.cpp 58 | src/cursor.hpp 59 | src/cursor.cpp 60 | src/popupmenu.hpp 61 | src/popupmenu.cpp 62 | src/cmdline.hpp 63 | src/cmdline.cpp 64 | src/font.hpp 65 | src/grid.cpp 66 | src/grid.hpp 67 | src/object.hpp 68 | src/object.cpp 69 | src/decide_renderer.hpp 70 | src/input.cpp 71 | src/input.hpp 72 | src/scalers.hpp 73 | src/lru.hpp 74 | src/qpaintgrid.hpp 75 | src/qpaintgrid.cpp 76 | src/animation.hpp 77 | src/animation.cpp 78 | src/types.hpp 79 | src/fontdesc.hpp 80 | src/mouse.hpp 81 | src/editor_base.cpp 82 | src/editor_base.hpp 83 | src/qt_editorui_base.cpp 84 | src/qt_editorui_base.hpp 85 | src/nvim_utils.hpp 86 | src/qeditor.hpp 87 | src/qeditor.cpp 88 | resources.qrc 89 | src/config.hpp 90 | src/config.cpp 91 | ) 92 | if (WIN32) 93 | message("Windows detected.") 94 | file(GLOB WINONLYSOURCES 95 | src/platform/windows/*.cpp 96 | src/platform/windows/*.hpp 97 | ) 98 | list(APPEND SOURCES ${WINONLYSOURCES}) 99 | endif() 100 | find_package(Boost COMPONENTS filesystem thread REQUIRED) 101 | if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Release") 102 | add_executable(nvui WIN32 "assets/icons/desktop/neovim_icon.rc" "src/main.cpp" ${SOURCES}) 103 | else() 104 | add_executable(nvui "src/main.cpp" ${SOURCES}) 105 | endif() 106 | target_link_libraries(nvui PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Svg) 107 | target_link_libraries(nvui PRIVATE fmt::fmt) 108 | target_include_directories(nvui PRIVATE 109 | "${PROJECT_SOURCE_DIR}/src" 110 | ) 111 | if(WIN32) 112 | target_link_libraries(nvui PUBLIC 113 | d2d1 114 | dwrite 115 | dwmapi 116 | d3d11 117 | ) 118 | endif() 119 | if (Boost_FOUND) 120 | target_include_directories(nvui PRIVATE ${Boost_INCLUDE_DIR}) 121 | endif() 122 | target_link_libraries(nvui PRIVATE 123 | ${Boost_LIBRARIES} 124 | ) 125 | include(CheckIPOSupported) 126 | check_ipo_supported(RESULT LTOAvailable) 127 | if(LTOAvailable) 128 | set_property(TARGET nvui PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 129 | endif() 130 | 131 | if(BUILD_TESTS) 132 | find_package(Catch2 CONFIG REQUIRED) 133 | file(GLOB TEST_SOURCES 134 | test/test_*.cpp 135 | ) 136 | if (WIN32) 137 | file(GLOB WINONLYTESTSOURCES 138 | src/platform/windows/*.cpp 139 | src/platform/windows/*.hpp 140 | ) 141 | list(APPEND TEST_SOURCES ${WINONLYTESTSOURCES}) 142 | endif() 143 | add_executable(nvui_test ${SOURCES} ${TEST_SOURCES}) 144 | target_link_libraries(nvui_test PRIVATE Catch2::Catch2) 145 | target_link_libraries(nvui_test PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Svg) 146 | target_link_libraries(nvui_test PRIVATE fmt::fmt) 147 | if (WIN32) 148 | target_link_libraries(nvui_test PUBLIC 149 | d2d1 150 | dwrite 151 | dwmapi 152 | d3d11 153 | ) 154 | endif() 155 | target_include_directories(nvui_test PRIVATE 156 | "${PROJECT_SOURCE_DIR}/test" 157 | "${PROJECT_SOURCE_DIR}/src" 158 | ) 159 | if (BOOST_FOUND) 160 | target_include_directories(nvui_test PRIVATE ${Boost_INCLUDE_DIR}) 161 | endif() 162 | target_link_libraries(nvui_test PRIVATE 163 | Boost::thread 164 | Boost::filesystem 165 | ) 166 | include(CTest) 167 | include(Catch) 168 | catch_discover_tests(nvui_test) 169 | endif() 170 | -------------------------------------------------------------------------------- /src/window.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_GUI_HPP 2 | #define NVUI_GUI_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "decide_renderer.hpp" 12 | #include "nvim.hpp" 13 | #include "qeditor.hpp" 14 | #include "titlebar.hpp" 15 | #include "hlstate.hpp" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | class Window; 25 | 26 | #if defined(Q_OS_WIN) 27 | #include "platform/windows/d2deditor.hpp" 28 | #endif 29 | 30 | constexpr int tolerance = 10; //10px tolerance for resizing 31 | 32 | /// The main window class which holds the rest of the GUI components. 33 | /// Fundamentally, the Neovim area is just 1 big text box. 34 | /// However, there are additional features that we are trying to 35 | /// support. 36 | class Window : public QMainWindow 37 | { 38 | Q_OBJECT 39 | #if defined(USE_DIRECT2D) 40 | using EditorType = D2DEditor; 41 | #elif defined(USE_QPAINTER) 42 | using EditorType = QEditor; 43 | #endif 44 | public: 45 | Window( 46 | std::string nvim_path, 47 | std::vector nvim_args, 48 | std::unordered_map capabilities, 49 | int width, 50 | int height, 51 | bool size_set, 52 | bool custom_titlebar, 53 | QWidget* parent = nullptr 54 | ); 55 | public slots: 56 | void resize_or_move(const QPointF& p); 57 | /** 58 | * Returns whether the window is frameless or not 59 | */ 60 | inline bool is_frameless() const 61 | { 62 | return windowFlags() & Qt::FramelessWindowHint; 63 | } 64 | 65 | /** 66 | * Maximize the window. 67 | */ 68 | void maximize(); 69 | /** 70 | * Connects to the signals emitted 71 | * by the editor. 72 | * These signals get emitted by its UISignaller. 73 | */ 74 | void connect_editor_signals(EditorType&); 75 | private: 76 | /// Save current window state and settings. 77 | void save_state(); 78 | /// Load saved config state, if it exists. 79 | /// Returns true if any values were set. 80 | bool load_config(); 81 | /** 82 | * Disable the frameless window. 83 | * The window should be in frameless mode, 84 | * so windowState() & Qt::FramelessWindowHint should be true. 85 | */ 86 | void disable_frameless_window(); 87 | /** 88 | * Enable the frameless window. 89 | * The window should not be in frameless window mode, 90 | * so windowState() & Qt::FramelessWindowHint should be false. 91 | */ 92 | void enable_frameless_window(); 93 | /** 94 | * Hides the title bar. 95 | * This just always hides it, 96 | * since there is no reason not to, unlike with showing. 97 | */ 98 | inline void hide_title_bar() 99 | { 100 | title_bar->hide(); 101 | } 102 | 103 | /** 104 | * Shows the titlebar. Only activates if the window is a 105 | * frameless window (otherwise you would get two title bars, 106 | * the OS one and the custom one). 107 | */ 108 | inline void show_title_bar() 109 | { 110 | if (!is_frameless()) return; 111 | title_bar->show(); 112 | } 113 | 114 | /** 115 | * If fullscreen is true, shows the window in fullscreen mode, 116 | * otherwise reverts back to how it was before. 117 | */ 118 | void set_fullscreen(bool fullscreen); 119 | /** 120 | * Turn the window fullscreen. 121 | * If the window is already fullscreen, this does nothing. 122 | */ 123 | void fullscreen(); 124 | /** 125 | * Un-fullscreen the window, restoring its original state. 126 | */ 127 | void un_fullscreen(); 128 | /** 129 | * Updates the title bar colors. 130 | * If titlebar_colors contains a value, then it uses 131 | * titlebar_colors's values. 132 | * Otherwise, it uses the default colors. 133 | */ 134 | void update_titlebar_colors(QColor fg, QColor bg); 135 | void remove_editor(EditorType* editor); 136 | void make_active_editor(int index); 137 | void create_editor( 138 | int width, int height, std::string nvim_path, std::vector args, 139 | std::unordered_map capabilities 140 | ); 141 | void select_editor_from_dialog(); 142 | QtEditorUIBase& current_editor(); 143 | void update_default_colors(QColor fg, QColor bg); 144 | bool resizing; 145 | bool maximized = false; 146 | bool moving = false; 147 | std::unique_ptr title_bar; 148 | QFlags prev_state; 149 | QColor default_fg = Qt::white; 150 | QColor default_bg = Qt::black; 151 | template 152 | using opt = std::optional; 153 | std::pair, opt> titlebar_colors; 154 | QStackedWidget* editor_stack; 155 | signals: 156 | void win_state_changed(Qt::WindowStates new_state); 157 | void default_colors_changed(QColor fg, QColor bg); 158 | protected: 159 | bool nativeEvent(const QByteArray& e_type, void* msg, long* result) override; 160 | void changeEvent(QEvent* event) override; 161 | void mousePressEvent(QMouseEvent* event) override; 162 | void mouseReleaseEvent(QMouseEvent* event) override; 163 | void mouseMoveEvent(QMouseEvent* event) override; 164 | void resizeEvent(QResizeEvent* event) override; 165 | void moveEvent(QMoveEvent* event) override; 166 | void closeEvent(QCloseEvent* event) override; 167 | }; 168 | 169 | #endif // NVUI_WINDOW_HPP 170 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_UTILS_HPP 2 | #define NVUI_UTILS_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // Creates a QIcon from the given svg, with the given foreground 18 | // and background color. 19 | // Assumption: The only color in the file is black ("#000"). 20 | inline std::optional icon_from_svg( 21 | const QString filename, 22 | const QColor& foreground, 23 | const QColor& background = QColor(0, 0, 0, 0), 24 | int width = -1, 25 | int height = -1 26 | ) 27 | { 28 | QFile file {filename}; 29 | if (!file.exists()) 30 | { 31 | return std::nullopt; 32 | } 33 | file.open(QIODevice::ReadOnly); 34 | QString text = file.readAll(); 35 | QSvgRenderer renderer {text.toUtf8()}; 36 | if (width <= 0) width = renderer.defaultSize().width(); 37 | if (height <= 0) height = renderer.defaultSize().height(); 38 | QPixmap pm {width, height}; 39 | pm.fill(background); 40 | QPainter painter(&pm); 41 | renderer.render(&painter); 42 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 43 | painter.fillRect(pm.rect(), foreground); 44 | return QIcon {pm}; 45 | } 46 | 47 | inline std::optional pixmap_from_svg( 48 | const QString& filename, 49 | const QColor& foreground, 50 | const QColor& background = Qt::transparent, 51 | int width = 0, 52 | int height = 0 53 | ) 54 | { 55 | QFile file {filename}; 56 | if (!file.exists()) return std::nullopt; 57 | file.open(QIODevice::ReadOnly); 58 | QString data = file.readAll(); 59 | QSvgRenderer renderer {data.toUtf8()}; 60 | QPixmap pm {width, height}; 61 | if (pm.isNull()) return std::nullopt; 62 | pm.fill(Qt::transparent); 63 | QPainter p(&pm); 64 | renderer.render(&p); 65 | p.setCompositionMode(QPainter::CompositionMode_SourceIn); 66 | p.fillRect(pm.rect(), foreground); 67 | if (background.alpha() == 0) return pm; 68 | p.end(); 69 | QPixmap filled {width, height}; 70 | filled.fill(background); 71 | QPainter painter(&filled); 72 | painter.drawPixmap(filled.rect(), pm, pm.rect()); 73 | return filled; 74 | } 75 | 76 | // Macro to time how long something takes 77 | // using std::chrono (only in debug mode) 78 | #ifndef NDEBUG 79 | #define TIME(expr) \ 80 | using Clock = ::std::chrono::high_resolution_clock; \ 81 | const auto start = Clock::now(); \ 82 | (expr); \ 83 | const auto end = Clock::now(); \ 84 | ::std::cout << "Took " << ::std::chrono::duration(end - start).count() << " ms.\n"; 85 | #else 86 | #define TIME(expr) (expr) 87 | #endif // NDEBUG 88 | 89 | inline QString normalize_path(const QString& path) 90 | { 91 | return QCoreApplication::applicationDirPath() + "/" + path; 92 | } 93 | 94 | /// Resizes the 1d vector with size (prev_rows * prev_cols) to the 95 | /// size (rows * cols) in the same way that a 2d vector would be resized. 96 | /// This means that if cols < prev_cols, for example, the extra columns 97 | /// are cut off from the right. 98 | /// I think this is needed for grid resizing. 99 | template::size_type> 100 | void resize_1d_vector( 101 | std::vector& v, 102 | Size_Type cols, 103 | Size_Type rows, 104 | Size_Type prev_cols, 105 | Size_Type prev_rows, 106 | T default_obj = {} 107 | ) 108 | { 109 | if (v.size() / prev_cols != prev_rows) return; 110 | std::vector new_v; 111 | new_v.resize(cols * rows, default_obj); 112 | for(int i = 0; i < std::min(rows, prev_rows); ++i) 113 | { 114 | for(int j = 0; j < std::min(cols, prev_cols); ++j) 115 | { 116 | new_v[i * cols + j] = v[i * prev_cols + j]; 117 | } 118 | } 119 | v.swap(new_v); 120 | } 121 | 122 | template 123 | msgpack::object_handle pack(const T& obj) 124 | { 125 | msgpack::sbuffer sbuf; 126 | msgpack::pack(sbuf, obj); 127 | return msgpack::unpack(sbuf.data(), sbuf.size()); 128 | } 129 | 130 | /// Thanks Neovim-Qt 131 | /// https://github.com/equalsraf/neovim-qt/blob/master/src/gui/shellwidget/shellwidget.cpp#L42-L50 132 | /// Returns a monospace font family. 133 | inline QString default_font_family() 134 | { 135 | #if defined(Q_OS_MAC) 136 | return "Courier New"; 137 | #elif defined(Q_OS_WIN) 138 | return "Consolas"; 139 | #else 140 | return "Monospace"; 141 | #endif 142 | } 143 | 144 | template 145 | void wait_for_value(std::atomic& v, T val) 146 | { 147 | while(v != val) {} 148 | } 149 | 150 | // find or default for map containers 151 | template 152 | V find_or_default(const Map& m, const K& k, const V& v) 153 | { 154 | auto it = m.find(k); 155 | if (it == m.end()) return v; 156 | return it->second; 157 | } 158 | 159 | template 160 | void for_each_in_tuple(std::tuple& t, Func&& f) 161 | { 162 | constexpr auto s = std::integral_constant {}; 163 | f(std::get(t)); 164 | if constexpr(idx != sizeof...(Types) - 1) 165 | { 166 | for_each_in_tuple(t, std::forward(f)); 167 | } 168 | } 169 | 170 | /// UCS2-aware string reversal 171 | inline void reverse_qstring(QString& s) 172 | { 173 | const int len = s.size(); 174 | for(int i = 0; i < len / 2; ++i) 175 | { 176 | if (s.at(i).isHighSurrogate()) 177 | { 178 | QChar hi = s[i]; 179 | QChar low = s[i + 1]; 180 | s[i] = s[len - i - 2]; 181 | s[i + 1] = s[len - i - 1]; 182 | s[len - i - 2] = hi; 183 | s[len - i - 1] = low; 184 | i += 2; 185 | } 186 | else 187 | { 188 | QChar temp = s[i]; 189 | s[i] = s[len - i - 1]; 190 | s[len - i - 1] = temp; 191 | } 192 | } 193 | } 194 | 195 | #endif // NVUI_UTILS_HPP 196 | -------------------------------------------------------------------------------- /src/hlstate.cpp: -------------------------------------------------------------------------------- 1 | #include "hlstate.hpp" 2 | #include "utils.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace hl 8 | { 9 | HLAttr hl_attr_from_object(const Object& obj) 10 | { 11 | using u32 = std::uint32_t; 12 | const auto* arr = obj.array(); 13 | assert(arr && arr->size() >= 4); 14 | assert(arr->at(0).convertible()); 15 | const int id = (int) arr->at(0); 16 | auto* map_ptr = arr->at(1).map(); 17 | if (!map_ptr) return {}; 18 | const ObjectMap& map = *map_ptr; 19 | HLAttr attr {id}; 20 | if (map.contains("foreground")) 21 | { 22 | assert(map.at("foreground").convertible()); 23 | attr.foreground = (u32) map.at("foreground"); 24 | } 25 | if (map.contains("background")) 26 | { 27 | assert(map.at("background").convertible()); 28 | attr.background = (u32) map.at("background"); 29 | } 30 | if (map.contains("reverse")) attr.reverse = true; 31 | if (map.contains("special")) 32 | { 33 | assert(map.at("special").convertible()); 34 | attr.special = (u32) map.at("special"); 35 | } 36 | if (map.contains("italic")) attr.font_opts |= FontOpts::Italic; 37 | if (map.contains("bold")) attr.font_opts |= FontOpts::Bold; 38 | if (map.contains("underline")) attr.font_opts |= FontOpts::Underline; 39 | if (map.contains("strikethrough")) 40 | { 41 | attr.font_opts |= FontOpts::Strikethrough; 42 | } 43 | if (map.contains("undercurl")) attr.font_opts |= FontOpts::Undercurl; 44 | auto* info_arr = arr->at(3).array(); 45 | if (!arr) return attr; 46 | for(const auto& o : *info_arr) 47 | { 48 | AttrState state; 49 | if (auto* hi_name = o.try_at("hi_name").string()) 50 | { 51 | state.hi_name = *hi_name; 52 | } 53 | if (auto* ui_name = o.try_at("ui_name").string()) 54 | { 55 | state.ui_name = *ui_name; 56 | } 57 | if (auto* kind = o.try_at("kind").string()) 58 | { 59 | state.hi_name = *kind == "syntax" 60 | ? Kind::Syntax 61 | : Kind::UI; 62 | } 63 | if (auto hid = o.try_at("id").try_convert()) 64 | { 65 | state.id = hid.value(); 66 | } 67 | attr.state.push_back(std::move(state)); 68 | } 69 | return attr; 70 | } 71 | } // namespace hl 72 | 73 | const HLAttr& HLState::attr_for_id(int id) const 74 | { 75 | if (id < 0 || id >= (int) id_to_attr.size()) return default_colors; 76 | return id_to_attr[id]; 77 | } 78 | 79 | int HLState::id_for_name(const std::string &name) const 80 | { 81 | const auto it = name_to_id.find(name); 82 | if (it != name_to_id.end()) 83 | { 84 | return it->second; 85 | } 86 | return 0; 87 | } 88 | 89 | void HLState::set_name_id(const std::string& name, std::uint32_t hl_id) 90 | { 91 | name_to_id[name] = hl_id; 92 | } 93 | 94 | void HLState::set_id_attr(int id, HLAttr attr) 95 | { 96 | if (id > (int) id_to_attr.size()) 97 | { 98 | // Shouldn't happen with the way Neovim gives us highlight 99 | // attributes but just to make sure 100 | id_to_attr.resize((std::size_t) id + 1); 101 | } 102 | if (id == (int) id_to_attr.size()) 103 | { 104 | id_to_attr.emplace_back(std::move(attr)); 105 | return; 106 | } 107 | id_to_attr[id] = std::move(attr); 108 | } 109 | 110 | void HLState::default_colors_set(const Object& obj) 111 | { 112 | // We only look at the first three values (the others are ctermfg 113 | // and ctermbg, which we don't care about) 114 | auto* arr = obj.array(); 115 | if (!arr || arr->size() < 3) return; 116 | auto fg = arr->at(0).try_convert(); 117 | auto bg = arr->at(1).try_convert(); 118 | auto sp = arr->at(2).try_convert(); 119 | assert(fg && bg && sp); 120 | default_colors.foreground = *fg; 121 | default_colors.background = *bg; 122 | default_colors.special = *sp; 123 | } 124 | 125 | const HLAttr& HLState::default_colors_get() const 126 | { 127 | return default_colors; 128 | } 129 | 130 | void HLState::define(const Object& obj) 131 | { 132 | HLAttr attr = hl::hl_attr_from_object(obj); 133 | int id = attr.hl_id; 134 | for(const AttrState& s : attr.state) 135 | { 136 | if (!s.hi_name.empty()) 137 | { 138 | set_name_id(s.hi_name, id); 139 | } 140 | if (!s.ui_name.empty()) 141 | { 142 | set_name_id(s.ui_name, id); 143 | } 144 | } 145 | set_id_attr(attr.hl_id, std::move(attr)); 146 | } 147 | 148 | void HLState::group_set(const Object& obj) 149 | { 150 | auto* arr = obj.array(); 151 | assert(arr && arr->size() >= 2); 152 | auto* name = arr->at(0).string(); 153 | auto* id = arr->at(1).u64(); 154 | if (!name || !id) return; 155 | auto hl_name = *name; 156 | auto hl_id = static_cast(*id); 157 | set_name_id(std::move(hl_name), hl_id); 158 | } 159 | 160 | HLAttr::ColorPair HLState::colors_for(const HLAttr& attr) const 161 | { 162 | return attr.fg_bg(default_colors); 163 | } 164 | 165 | namespace font 166 | { 167 | template<> 168 | void set_opts(QFont& font, const FontOptions opts) 169 | { 170 | font.setItalic(opts & FontOpts::Italic); 171 | font.setBold(opts & FontOpts::Bold); 172 | font.setStrikeOut(opts & FontOpts::Strikethrough); 173 | font.setUnderline(opts & FontOpts::Underline); 174 | } 175 | template<> 176 | void set_opts(QFont& font, const FontOptions opts) 177 | { 178 | font.setItalic(opts & FontOpts::Italic); 179 | font.setBold(opts & FontOpts::Bold); 180 | } 181 | 182 | #define RETURN_FLAG(opt, flag) \ 183 | if ((opt) & (flag)) return (flag); 184 | 185 | FontOpts weight_for(const FontOptions& fo) 186 | { 187 | RETURN_FLAG(fo, FontOpts::Normal); 188 | RETURN_FLAG(fo, FontOpts::Thin); 189 | RETURN_FLAG(fo, FontOpts::Light); 190 | RETURN_FLAG(fo, FontOpts::Bold); 191 | RETURN_FLAG(fo, FontOpts::SemiBold); 192 | RETURN_FLAG(fo, FontOpts::Medium); 193 | RETURN_FLAG(fo, FontOpts::ExtraBold); 194 | return FontOpts::Normal; 195 | } 196 | 197 | FontOpts style_for(const FontOptions& fo) 198 | { 199 | RETURN_FLAG(fo, FontOpts::Italic); 200 | return FontOpts::Normal; 201 | } 202 | } // namespace font 203 | 204 | -------------------------------------------------------------------------------- /src/grid.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_GRID_HPP 2 | #define NVUI_GRID_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "scalers.hpp" 13 | 14 | using grid_char = QString; 15 | 16 | struct GridChar 17 | { 18 | int hl_id; // Shouldn't have more than 65k highlight attributes 19 | grid_char text; 20 | bool double_width = false; 21 | std::uint32_t ucs; 22 | static grid_char grid_char_from_str(const std::string& s); 23 | }; 24 | 25 | // Differentiate between redrawing and clearing (since clearing is 26 | // a lot easier) 27 | enum class PaintKind : std::uint8_t 28 | { 29 | Clear, 30 | Draw, 31 | Redraw, 32 | Scroll 33 | }; 34 | 35 | struct ScrollEventInfo 36 | { 37 | QRect rect; 38 | // dx and dy are how many columns/rows to move right/down. 39 | // Positive values indicate right/down, 40 | // and negative values indicate left/up. 41 | int dx; 42 | int dy; 43 | }; 44 | 45 | struct DrawEventInfo 46 | { 47 | QRect rect; 48 | }; 49 | 50 | struct RedrawEventInfo {}; 51 | 52 | struct ClearEventInfo 53 | { 54 | QRect rect; 55 | }; 56 | 57 | struct PaintEventItem 58 | { 59 | bool is_scroll_event() const { return type == PaintKind::Scroll; } 60 | bool is_redraw_event() const { return type == PaintKind::Redraw; } 61 | bool is_clear_event() const { return type == PaintKind::Clear; } 62 | bool is_draw_event() const { return type == PaintKind::Draw; } 63 | const auto& scroll_info() const { return std::get(event); } 64 | const auto& draw_info() const { return std::get(event); } 65 | const auto& redraw_info() const { return std::get(event); } 66 | const auto& clear_info() const { return std::get(event); } 67 | PaintKind type; 68 | std::variant< 69 | ScrollEventInfo, DrawEventInfo, RedrawEventInfo, ClearEventInfo 70 | > event; 71 | }; 72 | 73 | struct Viewport 74 | { 75 | std::uint32_t topline; 76 | std::uint32_t botline; 77 | std::uint32_t curline; 78 | std::uint32_t curcol; 79 | }; 80 | 81 | 82 | /// The base grid object, no rendering functionality. 83 | /// Contains some convenience functions for setting text, 84 | /// position, size, etc. 85 | /// The event queue contains the rendering commands to 86 | /// be performed. 87 | class GridBase : public QObject 88 | { 89 | Q_OBJECT 90 | public: 91 | /// For floating window ordering 92 | struct FloatOrderInfo 93 | { 94 | bool operator<(const FloatOrderInfo& other) const; 95 | int zindex; 96 | double x; 97 | double y; 98 | }; 99 | using u16 = std::uint16_t; 100 | using u32 = std::uint32_t; 101 | using u64 = std::uint64_t; 102 | GridBase( 103 | double x, 104 | double y, 105 | u16 w, 106 | u16 h, 107 | u16 id 108 | ); 109 | virtual ~GridBase() = default; 110 | void set_text( 111 | grid_char c, 112 | u16 row, 113 | u16 col, 114 | int hl_id, 115 | u16 repeat, 116 | bool is_dbl_width 117 | ); 118 | /** 119 | * Set the size of the grid (cols x rows) 120 | * to width x height. 121 | */ 122 | virtual void set_size(u16 w, u16 h); 123 | /** 124 | * Set the position of the grid in terms of 125 | * which row and column it starts on. 126 | */ 127 | virtual void set_pos(double x, double y); 128 | void set_pos(QPoint p); 129 | /// Send a redraw message to the grid 130 | void send_redraw(); 131 | void send_clear(); 132 | void send_draw(QRect r); 133 | /// Grid's top left position 134 | QPoint top_left(); 135 | QPoint bot_right(); 136 | /// Grid's bottom right position 137 | QPoint bot_left(); 138 | QPoint top_right(); 139 | /// Clear the event queue 140 | void clear_event_queue(); 141 | /// Change the viewport to the new viewport. 142 | virtual void viewport_changed(Viewport vp); 143 | bool is_float() const; 144 | void set_floating(bool f) noexcept; 145 | void win_pos(double x, double y); 146 | void float_pos(double x, double y); 147 | void msg_set_pos(double x, double y); 148 | void set_float_ordering_info(int zindex, const QPointF& p) noexcept; 149 | bool operator<(const GridBase& other) const noexcept; 150 | void scroll(int top, int bot, int left, int right, int rows); 151 | void clear(); 152 | public: 153 | double x; 154 | double y; 155 | u16 cols; 156 | u16 rows; 157 | u16 id; 158 | std::size_t z_index = 0; 159 | std::int64_t winid = 0; 160 | std::vector area; // Size = rows * cols 161 | bool hidden = false; 162 | std::queue evt_q; 163 | Viewport viewport; 164 | bool is_float_grid = false; 165 | FloatOrderInfo float_ordering_info; 166 | /// Not used in GridBase (may not even be used at all 167 | /// if animations are not supported/enabled by the rendering 168 | /// grid). 169 | static scalers::time_scaler scroll_scaler; 170 | static scalers::time_scaler move_scaler; 171 | protected: 172 | // Flag that is set to "true" when the grid gets "modified" 173 | // To fix janky scrolling. 174 | // The problem is caused by Neovim sending 2 win_viewport events 175 | // One win_viewport event is sent before any grid modification is done 176 | // which causes the original grid snapshot to be drawn one line lower 177 | // than it should be 178 | // Then Neovim sends out the grid_line events but if the lag between 179 | // the two events is high enough there will be a delay and you will see 180 | // a frame of the old snapshot being drawn one line below which then 181 | // gets drawn over, and it looks bad. 182 | bool modified = false; 183 | bool is_msg_grid = false; 184 | // scroll_rect describes the original rectangle 185 | // rows is how many rows to scroll by in the up-direction 186 | // like how Neovim uses it in 'grid_scroll' 187 | // cols is how many columns to scroll by, although it's 0 for now 188 | struct ScrollArgs 189 | { 190 | QRect scroll_rect; 191 | // Where the scrolled region begins 192 | QPoint top_left; 193 | int rows; 194 | int cols; 195 | }; 196 | static ScrollEventInfo convert_grid_scroll_args( 197 | int top, int bot, int left, int right, int rows, int cols = 0 198 | ); 199 | }; 200 | 201 | #endif // NVUI_GRID_HPP 202 | -------------------------------------------------------------------------------- /src/hlstate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_HLSTATE_HPP 2 | #define NVUI_HLSTATE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "types.hpp" 15 | #include "object.hpp" 16 | 17 | using uint8 = std::uint8_t; 18 | using uint16 = std::uint16_t; 19 | using uint32 = std::uint32_t; 20 | using uint64 = std::uint64_t; 21 | 22 | enum Kind 23 | { 24 | Syntax, 25 | UI 26 | }; 27 | 28 | struct Color 29 | { 30 | uint8 r; 31 | uint8 g; 32 | uint8 b; 33 | Color() = default; 34 | Color(uint32 clr) 35 | : r((clr & 0x00ff0000) >> 16), 36 | g((clr & 0x0000ff00) >> 8), 37 | b((clr & 0x000000ff)) 38 | { 39 | } 40 | Color(u8 rr, u8 gg, u8 bb): r(rr), g(gg), b(bb) {} 41 | Color(int clr) : Color(static_cast(clr)) {} 42 | Color(uint64 clr) : Color(static_cast(clr)) {} 43 | /** 44 | * Converts a Color back to a uint32 45 | */ 46 | uint32 to_uint32() const 47 | { 48 | return r << 16 | g << 8 | b; 49 | } 50 | 51 | QColor qcolor() const 52 | { 53 | return {r, g, b}; 54 | } 55 | 56 | bool operator==(const Color& other) const 57 | { 58 | return r == other.r && b == other.b && g == other.g; 59 | } 60 | private: 61 | }; 62 | 63 | struct AttrState 64 | { 65 | Kind kind = Kind::Syntax; 66 | std::string hi_name; 67 | std::string ui_name; 68 | int id = 0; 69 | }; 70 | 71 | enum FontOpts : u16 72 | { 73 | Normal = 1, 74 | Bold = 2, 75 | Italic = 4, 76 | Underline = 16, 77 | Strikethrough = 32, 78 | Undercurl = 64, 79 | Thin = 128, 80 | Light = 256, 81 | Medium = 512, 82 | SemiBold = 1024, 83 | ExtraBold = 2048, 84 | }; 85 | 86 | using FontOptions = std::underlying_type_t; 87 | static const Color NVUI_WHITE = 0x00ffffff; 88 | static const Color NVUI_BLACK = 0; 89 | 90 | /// Data for a single highlight attribute 91 | class HLAttr 92 | { 93 | public: 94 | struct ColorPair 95 | { 96 | Color fg; 97 | Color bg; 98 | }; 99 | struct ColorTriplet 100 | { 101 | Color fg; 102 | Color bg; 103 | Color sp; 104 | }; 105 | std::optional fg() const { return foreground; } 106 | std::optional bg() const { return background; } 107 | std::optional sp() const { return special; } 108 | bool italic() const { return font_opts & FontOpts::Italic; } 109 | bool bold() const { return font_opts & FontOpts::Bold; } 110 | bool strikethrough() const { return font_opts & FontOpts::Strikethrough; } 111 | bool underline() const { return font_opts & FontOpts::Underline; } 112 | bool undercurl() const { return font_opts & FontOpts::Undercurl; } 113 | ColorPair fg_bg(const HLAttr& fallback) const 114 | { 115 | ColorPair cp = { 116 | foreground.value_or(fallback.foreground.value_or(NVUI_WHITE)), 117 | background.value_or(fallback.background.value_or(NVUI_BLACK)) 118 | }; 119 | if (reverse) std::swap(cp.fg, cp.bg); 120 | return cp; 121 | } 122 | ColorTriplet fg_bg_sp(const HLAttr& fallback) const 123 | { 124 | auto&& [fg, bg] = fg_bg(fallback); 125 | return { 126 | fg, bg, special.value_or(fg) 127 | }; 128 | } 129 | int hl_id = 0; 130 | FontOptions font_opts = FontOpts::Normal; 131 | bool reverse = false; 132 | std::optional special {}; 133 | std::optional foreground {}; 134 | std::optional background {}; 135 | /// We don't need a detailed view of the highlight state 136 | // right now so we won't do anything with this. 137 | std::vector state {}; 138 | float opacity = 1; 139 | }; 140 | 141 | /// Keeps the highlight state of Neovim 142 | /// HlState is essentially a map of highlight names to their 143 | /// corresponding id's, and a secondary map of id's to 144 | /// the HLAttr they correspond to. 145 | class HLState 146 | { 147 | public: 148 | HLState() { 149 | id_to_attr.reserve(1000); 150 | } 151 | /** 152 | * Maps name to hl_id. 153 | * This function maps to "hl_group_set". 154 | */ 155 | void set_name_id(const std::string& name, std::uint32_t hl_id); 156 | /** 157 | * Maps id to attr. 158 | */ 159 | void set_id_attr(int id, HLAttr attr); 160 | /** 161 | * Returns the highlight attribute for the given id. 162 | */ 163 | const HLAttr& attr_for_id(int id) const; 164 | /** 165 | * Returns the name of the highlight group for the given id. 166 | */ 167 | int id_for_name(const std::string& name) const; 168 | /** 169 | * Manages an "hl_attr_define" call, with obj 170 | * being the parameters of the call. 171 | */ 172 | void define(const Object& obj); 173 | /** 174 | * Sets the default colors. 175 | */ 176 | void default_colors_set(const Object& obj); 177 | /** 178 | * Sets the given highlight group. This should be called with 179 | * the parameters of an "hl_group_set" call. 180 | */ 181 | void group_set(const Object& obj); 182 | /** 183 | * Returns the default colors. 184 | */ 185 | const HLAttr& default_colors_get() const; 186 | Color default_bg() const { return default_colors.bg().value_or(NVUI_BLACK); } 187 | Color default_fg() const { return default_colors.fg().value_or(NVUI_WHITE); } 188 | HLAttr::ColorPair colors_for(const HLAttr& attr) const; 189 | private: 190 | HLAttr default_colors; 191 | std::unordered_map name_to_id; 192 | //std::unordered_map id_to_attr; 193 | std::vector id_to_attr {1}; 194 | }; 195 | 196 | /// Defining a function to parse "hl_attr_define" data 197 | /// into an HLState (for startup, after that 198 | /// we modify the initial state). 199 | namespace hl 200 | { 201 | /** 202 | * Produces an HLAttr from the given object. 203 | * obj must be of type msgpack::type::ARRAY, 204 | * and should only be called with arrays 205 | * that were the parameters of an "hl_attr_define" 206 | * call. 207 | */ 208 | HLAttr hl_attr_from_object(const Object& obj); 209 | } 210 | 211 | namespace font 212 | { 213 | template 214 | void set_opts(QFont& font, const FontOptions options); 215 | FontOpts weight_for(const FontOptions&); 216 | FontOpts style_for(const FontOptions&); 217 | } 218 | 219 | #endif 220 | -------------------------------------------------------------------------------- /src/platform/windows/direct2dpaintgrid.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_PLATFORM_WINDOWS_DIRECT2DPAINTGRID_HPP 2 | #define NVUI_PLATFORM_WINDOWS_DIRECT2DPAINTGRID_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "grid.hpp" 11 | #include "hlstate.hpp" 12 | #include "lru.hpp" 13 | #include 14 | 15 | class D2DEditor; 16 | class Cursor; 17 | 18 | /// Windows only class, uses Direct2D and DirectWrite. 19 | /// This grid paints onto an ID2D1Bitmap object and works 20 | /// with the WinEditorArea class to draw these bitmaps 21 | /// to the screen (HWND). 22 | class D2DPaintGrid : public GridBase 23 | { 24 | Q_OBJECT 25 | template 26 | using ComPtr = Microsoft::WRL::ComPtr; 27 | struct Snapshot 28 | { 29 | Viewport vp; 30 | ComPtr image; 31 | }; 32 | template 33 | struct WinDeleter 34 | { 35 | void operator()(Resource** res) const 36 | { 37 | if (*res) 38 | { 39 | (*res)->Release(); 40 | *res = nullptr; 41 | } 42 | } 43 | }; 44 | using TextLayoutDeleter = WinDeleter; 45 | public: 46 | using GridBase::u16; 47 | using GridBase::u32; 48 | using d2pt = D2D1_POINT_2F; 49 | using d2rect = D2D1_RECT_F; 50 | using d2color = D2D1::ColorF; 51 | using cache_type = 52 | LRUCache, IDWriteTextLayout1*, TextLayoutDeleter>; 53 | public: 54 | template 55 | D2DPaintGrid(D2DEditor* wea, GridBaseArgs... args) 56 | : GridBase(args...), 57 | editor_area(wea), 58 | layout_cache(2000) 59 | { 60 | update_render_target(); 61 | init_connections(); 62 | initialize_scroll_animation(); 63 | initialize_move_animation(); 64 | } 65 | ~D2DPaintGrid(); 66 | /// Returns the position of the top-left corner of the grid. 67 | /// (Pixel position). 68 | d2pt pos() const; 69 | /// Returns the grid's rectangle position relative to the top-left 70 | /// corner of the editor area. 71 | d2rect rect() const; 72 | /// Returns the grid's source rectangle. 73 | d2rect source_rect() const; 74 | /// Process the event queue, painting the updates 75 | /// to the bitmap. 76 | void process_events(); 77 | void set_size(u16 w, u16 h) override; 78 | void set_pos(double new_x, double new_y) override; 79 | void viewport_changed(Viewport vp) override; 80 | /// Update the top_left position of the grid. This is in terms 81 | /// of text so to get the pixel position you would need to multiply 82 | /// by the dimensions of the font being used. Doubles are used to 83 | /// allow for better pixel precision. 84 | void update_position(double x, double y); 85 | /// Render this grid to a render target 86 | void render(ID2D1DeviceContext* render_target); 87 | /// Draw the given cursor to the render target at the appropriate position 88 | void draw_cursor(ID2D1RenderTarget* target, const Cursor& cursor); 89 | private: 90 | std::vector snapshots; 91 | D2DEditor* editor_area = nullptr; 92 | ComPtr render_target = nullptr; 93 | ComPtr bitmap = nullptr; 94 | QTimer move_update_timer {}; 95 | float move_animation_time = -1.f; // Number of seconds till animation ends 96 | QPointF top_left = {0, 0}; 97 | float start_scroll_y = 0.f; 98 | float current_scroll_y = 0.f; 99 | bool is_scrolling = false; 100 | float cur_left = 0.f; 101 | float cur_top = 0.f; 102 | float scroll_animation_time; 103 | QTimer scroll_animation_timer {}; 104 | float dest_move_x = 0.f; 105 | float dest_move_y = 0.f; 106 | float old_move_x = 0.f; 107 | float old_move_y = 0.f; 108 | float dest_scroll_y = 0.f; 109 | using FontOptions = decltype(HLAttr::font_opts); 110 | /// A lot of time is spent text shaping, we cache the created text 111 | /// layouts 112 | cache_type layout_cache; 113 | /// Update the size of the bitmap to match the 114 | /// grid size 115 | void update_render_target(); 116 | /// Initialize the device contexts and bitmap. 117 | void initialize_context(); 118 | /// Create connections 119 | void init_connections(); 120 | /// Initialize the move animation 121 | void initialize_move_animation(); 122 | /// Initialize the scroll animation 123 | void initialize_scroll_animation(); 124 | /// Draw the grid range given by the rect. 125 | /// Since we draw from the top-left, no offset is needed 126 | /// (unlike in QPaintGrid). 127 | void draw( 128 | ID2D1RenderTarget* context, 129 | QRect r, 130 | ID2D1SolidColorBrush* fg_brush, 131 | ID2D1SolidColorBrush* bg_brush 132 | ); 133 | /// Draw text onto the given device context, clipped 134 | /// between start and end. Background & foreground colors 135 | /// are controlled by the main and fallback highlight attributes. 136 | void draw_text_and_bg( 137 | ID2D1RenderTarget* context, 138 | const QString& buf, 139 | const HLAttr& main, 140 | const HLAttr& fallback, 141 | D2D1_POINT_2F start, 142 | D2D1_POINT_2F end, 143 | float font_width, 144 | float font_height, 145 | IDWriteTextFormat* text_format, 146 | ID2D1SolidColorBrush* fg_brush, 147 | ID2D1SolidColorBrush* bg_brush 148 | ); 149 | void draw_text( 150 | ID2D1RenderTarget& target, 151 | const QString& text, 152 | const Color& fg, 153 | const Color& sp, 154 | const FontOptions font_opts, 155 | D2D1_POINT_2F top_left, 156 | D2D1_POINT_2F bot_right, 157 | float font_width, 158 | float font_height, 159 | ID2D1SolidColorBrush& fg_brush, 160 | IDWriteTextFormat* text_format, 161 | bool clip = false 162 | ); 163 | void draw_bg( 164 | ID2D1RenderTarget& target, 165 | const Color& bg, 166 | D2D1_POINT_2F top_left, 167 | D2D1_POINT_2F bot_right, 168 | ID2D1SolidColorBrush& brush 169 | ); 170 | void draw_bg( 171 | ID2D1RenderTarget& target, 172 | const Color& bg, 173 | D2D1_RECT_F rect, 174 | ID2D1SolidColorBrush& brush 175 | ); 176 | void scroll_bitmap(const ScrollEventInfo& info); 177 | /// Returns a copy of src. 178 | /// NOTE: Must be released. 179 | ComPtr copy_bitmap(ID2D1Bitmap1* src); 180 | ComPtr copy_bitmap(ID2D1Bitmap1* src, D2D1_RECT_U rect); 181 | }; 182 | 183 | 184 | #endif // NVUI_PLATFORM_WINDOWS_DIRECT2DPAINTGRID_HPP 185 | -------------------------------------------------------------------------------- /src/grid.cpp: -------------------------------------------------------------------------------- 1 | #include "grid.hpp" 2 | #include "utils.hpp" 3 | 4 | scalers::time_scaler GridBase::scroll_scaler = scalers::oneminusexpo2negative10; 5 | scalers::time_scaler GridBase::move_scaler = scalers::oneminusexpo2negative10; 6 | 7 | bool GridBase::FloatOrderInfo::operator<(const FloatOrderInfo& other) const 8 | { 9 | return zindex == other.zindex 10 | ? x == other.x 11 | ? y == other.y 12 | ? true // Rip 13 | : y < other.y 14 | : x < other.x 15 | : zindex < other.zindex; 16 | } 17 | 18 | ScrollEventInfo GridBase::convert_grid_scroll_args( 19 | int top, 20 | int bot, 21 | int left, 22 | int right, 23 | int rows, 24 | int cols 25 | ) 26 | { 27 | // Positive means upward, 28 | // Negative is downward. 29 | // dy is negative when rows is positive, 30 | // and positive when rows is negative 31 | // Don't know about cols right now but it's not being used 32 | if (rows > 0) 33 | { 34 | return { 35 | QRect(QPoint(left, top + rows), QPoint(right, bot)), 36 | -cols, 37 | -rows, 38 | }; 39 | } 40 | else 41 | { 42 | return { 43 | QRect(QPoint(left, top), QPoint(right, bot + rows)), 44 | -cols, 45 | -rows, 46 | }; 47 | } 48 | } 49 | 50 | GridBase::GridBase( 51 | double x, 52 | double y, 53 | u16 w, 54 | u16 h, 55 | u16 id 56 | ) : x(x), 57 | y(y), 58 | cols(w), 59 | rows(h), 60 | id(id), 61 | area(w * h), 62 | viewport({0, 0, 0, 0}) 63 | { 64 | } 65 | 66 | bool GridBase::operator<(const GridBase& other) const noexcept 67 | { 68 | if (is_msg_grid) return false; 69 | else if (other.is_msg_grid) return true; 70 | else if (!is_float() && other.is_float()) 71 | { 72 | return true; 73 | } 74 | else if (is_float() && !other.is_float()) 75 | { 76 | return false; 77 | } 78 | else if (is_float() && other.is_float()) 79 | { 80 | return float_ordering_info < other.float_ordering_info; 81 | } 82 | else 83 | { 84 | return id < other.id; 85 | } 86 | } 87 | 88 | grid_char GridChar::grid_char_from_str(const std::string& s) 89 | { 90 | return QString::fromStdString(s); 91 | } 92 | 93 | void GridBase::scroll(int top, int bot, int left, int right, int rows) 94 | { 95 | if (rows > 0) 96 | { 97 | // Original region is 98 | // (top + rows) .. bot (exclusive) 99 | // Scrolled region is 100 | // top .. bot - rows 101 | for(int y = top; y < (bot - rows); ++y) 102 | { 103 | for(int x = left; x < right && x < cols; ++x) 104 | { 105 | area[y * cols + x] = std::move(area[(y + rows) * cols + x]); 106 | } 107 | } 108 | } 109 | else if (rows < 0) 110 | { 111 | // Original region is 112 | // top .. bot + rows (exclusive) 113 | // Scrolled region is 114 | // top - rows .. bot 115 | for(int y = (bot-1); y >= (top - rows); --y) 116 | { 117 | for(int x = left; x <= right && x < cols; ++x) 118 | { 119 | area[y * cols + x] = std::move(area[(y + rows) * cols + x]); 120 | } 121 | } 122 | } 123 | evt_q.push({PaintKind::Scroll, convert_grid_scroll_args(top, bot, left, right, rows)}); 124 | modified = true; 125 | } 126 | 127 | void GridBase::set_text( 128 | grid_char c, 129 | u16 row, 130 | u16 col, 131 | int hl_id, 132 | u16 repeat, 133 | bool is_dbl_width 134 | ) 135 | { 136 | std::uint32_t ucs; 137 | if (c.isEmpty()) ucs = 0; 138 | else 139 | { 140 | if (c.at(0).isHighSurrogate()) 141 | { 142 | assert(c.size() >= 2); 143 | ucs = QChar::surrogateToUcs4(c.at(0), c.at(1)); 144 | } 145 | else ucs = c.at(0).unicode(); 146 | } 147 | // Neovim should make sure this isn't out-of-bounds 148 | assert(col + repeat <= cols); 149 | for(std::uint16_t i = 0; i < repeat; ++i) 150 | { 151 | std::size_t idx = row * cols + col + i; 152 | if (idx >= area.size()) return; 153 | area[idx] = {hl_id, c, is_dbl_width, ucs}; 154 | } 155 | modified = true; 156 | } 157 | /** 158 | * Set the size of the grid (cols x rows) 159 | * to width x height. 160 | */ 161 | void GridBase::set_size(u16 w, u16 h) 162 | { 163 | static const GridChar empty_cell = {0, " ", false, QChar(' ').unicode()}; 164 | resize_1d_vector(area, w, h, cols, rows, empty_cell); 165 | cols = w; 166 | rows = h; 167 | } 168 | /** 169 | * Set the position of the grid in terms of 170 | * which row and column it starts on. 171 | */ 172 | void GridBase::set_pos(double new_x, double new_y) 173 | { 174 | x = new_x; 175 | y = new_y; 176 | } 177 | void GridBase::msg_set_pos(double x, double y) 178 | { 179 | is_msg_grid = true; 180 | set_pos(x, y); 181 | } 182 | void GridBase::set_pos(QPoint p) { set_pos(p.x(), p.y()); } 183 | /// Send a redraw message to the grid 184 | void GridBase::send_redraw() 185 | { 186 | clear_event_queue(); 187 | evt_q.push({PaintKind::Redraw, RedrawEventInfo {}}); 188 | } 189 | void GridBase::send_clear() 190 | { 191 | clear_event_queue(); 192 | evt_q.push({PaintKind::Clear, ClearEventInfo {}}); 193 | } 194 | void GridBase::send_draw(QRect r) 195 | { 196 | evt_q.push({PaintKind::Draw, DrawEventInfo {r}}); 197 | } 198 | /// Grid's top left position 199 | QPoint GridBase::top_left() { return QPoint(x, y); }; 200 | QPoint GridBase::bot_right() { return QPoint(x + cols, y + rows); } 201 | /// Grid's bottom right position 202 | QPoint GridBase::bot_left() { return QPoint(x, y + rows); } 203 | QPoint GridBase::top_right() { return QPoint( x + cols, y); } 204 | /// Clear the event queue 205 | void GridBase::clear_event_queue() 206 | { 207 | decltype(evt_q)().swap(evt_q); 208 | } 209 | /// Change the viewport to the new viewport. 210 | void GridBase::viewport_changed(Viewport vp) 211 | { 212 | viewport = vp; 213 | } 214 | bool GridBase::is_float() const { return is_float_grid; } 215 | void GridBase::set_floating(bool f) noexcept { is_float_grid = f; } 216 | 217 | void GridBase::win_pos(double x, double y) 218 | { 219 | set_floating(false); 220 | set_pos(x, y); 221 | } 222 | 223 | void GridBase::float_pos(double x, double y) 224 | { 225 | set_floating(true); 226 | set_pos(x, y); 227 | } 228 | 229 | void GridBase::set_float_ordering_info(int zindex, const QPointF& p) noexcept 230 | { 231 | float_ordering_info = {zindex, p.x(), p.y()}; 232 | } 233 | 234 | void GridBase::clear() 235 | { 236 | for(auto& gc : area) 237 | { 238 | gc = {0, " ", false, QChar(' ').unicode()}; 239 | } 240 | send_clear(); 241 | } 242 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "nvim.hpp" 17 | #include "window.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "config.hpp" 23 | 24 | using std::string; 25 | using std::vector; 26 | 27 | std::optional get_arg( 28 | const std::vector& args, 29 | const std::string_view prefix 30 | ) 31 | { 32 | for(const auto& arg : args) 33 | { 34 | if (arg == "--") return {}; 35 | if (arg.rfind(prefix, 0) == 0) 36 | { 37 | return std::string_view(arg).substr(prefix.size()); 38 | } 39 | } 40 | return std::nullopt; 41 | } 42 | 43 | bool extract_arg_bool( 44 | const vector& args, const std::string_view prefix, 45 | bool defaultval, bool notfound 46 | ) 47 | { 48 | if (auto arg = get_arg(args, fmt::format("{}=", prefix))) 49 | { 50 | return arg.value() == "true" ? true : false; 51 | } 52 | if (auto arg = get_arg(args, prefix); arg && arg->empty()) 53 | { 54 | return defaultval; 55 | } 56 | return notfound; 57 | } 58 | 59 | vector neovim_args(const vector& listofargs) 60 | { 61 | vector args; 62 | for(std::size_t i = 0; i < listofargs.size(); ++i) 63 | { 64 | const auto& arg = listofargs[i]; 65 | if (arg == "--") 66 | { 67 | for(std::size_t j = i + 1; j < listofargs.size(); ++j) 68 | { 69 | args.push_back(listofargs[j]); 70 | } 71 | return args; 72 | } 73 | /// Add it to the list if it's a filename that actually exists. 74 | /// Doesn't work for anything that starts with "--" 75 | else if (std::ifstream f {arg}; f && !arg.starts_with("--")) 76 | { 77 | args.push_back(arg); 78 | } 79 | } 80 | return args; 81 | } 82 | 83 | vector get_args(int argc, char** argv) 84 | { 85 | return vector(argv + 1, argv + argc); 86 | } 87 | 88 | void start_detached(int argc, char** argv) 89 | { 90 | if (argc < 1) 91 | { 92 | fmt::print("No arguments given, could not start in detached mode\n"); 93 | return; 94 | } 95 | QString prog_name = argv[0]; 96 | QStringList args; 97 | for(int i = 1; i < argc; ++i) 98 | { 99 | QString arg = argv[i]; 100 | /// Don't spawn processes infinitely 101 | if (arg != "--detached") args.append(arg); 102 | } 103 | QProcess p; 104 | p.setProgram(prog_name); 105 | p.setArguments(args); 106 | p.startDetached(); 107 | } 108 | 109 | std::optional> parse_geometry(std::string_view geom) 110 | { 111 | auto pos = geom.find('x'); 112 | if (pos == std::string::npos) return std::nullopt; 113 | int width=0, height=0; 114 | auto data = geom.data(); 115 | auto res = std::from_chars(data, data + pos, width); 116 | if (res.ec != std::errc()) return {}; 117 | res = std::from_chars(data + pos + 1, data + geom.size(), height); 118 | if (res.ec != std::errc()) return {}; 119 | return std::pair {width, height}; 120 | } 121 | 122 | bool is_executable(std::string_view path) 123 | { 124 | QFileInfo file_info {QString::fromStdString(std::string(path))}; 125 | return file_info.exists() && file_info.isExecutable(); 126 | } 127 | 128 | int main(int argc, char** argv) 129 | { 130 | QCoreApplication::setApplicationName("nvui"); 131 | QApplication app {argc, argv}; 132 | Config::init(); 133 | const auto args = get_args(argc, argv); 134 | #ifdef Q_OS_LINUX 135 | // See issue #21 136 | auto env = boost::this_process::environment(); 137 | if (env.find("FONTCONFIG_PATH") == env.end()) 138 | { 139 | env.set("FONTCONFIG_PATH", "/etc/fonts"); 140 | } 141 | #endif 142 | int width = 100; 143 | int height = 50; 144 | bool custom_titlebar = false; 145 | // Arguments to pass to nvim 146 | vector nvim_args {"--embed"}; 147 | vector cl_nvim_args = neovim_args(args); 148 | nvim_args.insert(nvim_args.end(), cl_nvim_args.begin(), cl_nvim_args.end()); 149 | string nvim_path = ""; 150 | std::unordered_map capabilities = { 151 | {"ext_tabline", false}, 152 | {"ext_multigrid", Config::get("multigrid", false).toBool()}, 153 | {"ext_cmdline", Config::get("cmdline", false).toBool()}, 154 | {"ext_popupmenu", Config::get("popupmenu", false).toBool()}, 155 | {"ext_linegrid", true}, 156 | {"ext_hlstate", false}, 157 | }; 158 | auto should_detach = get_arg(args, "--detached"); 159 | if (should_detach) 160 | { 161 | start_detached(argc, argv); 162 | return 0; 163 | } 164 | custom_titlebar = extract_arg_bool(args, "--titlebar", true, false); 165 | auto path_to_nvim = get_arg(args, "--nvim="); 166 | if (path_to_nvim && is_executable(*path_to_nvim)) 167 | { 168 | nvim_path = std::string(*path_to_nvim); 169 | } 170 | auto geometry = get_arg(args, "--geometry="); 171 | bool geometry_set = false; 172 | if (geometry) 173 | { 174 | auto parsed = parse_geometry(*geometry); 175 | if (parsed) 176 | { 177 | geometry_set = true; 178 | std::tie(width, height) = *parsed; 179 | } 180 | } 181 | for(auto& [key, val] : capabilities) 182 | { 183 | bool preset = val; 184 | val = extract_arg_bool(args, fmt::format("--{}", key), true, preset); 185 | } 186 | std::optional> window_size; 187 | auto winsize = get_arg(args, "--size="); 188 | if (winsize) 189 | { 190 | window_size = parse_geometry(winsize.value()); 191 | } 192 | std::ios_base::sync_with_stdio(false); 193 | try 194 | { 195 | Window w {nvim_path, nvim_args, capabilities, width, height, geometry_set, custom_titlebar}; 196 | if (window_size) w.resize(window_size->first, window_size->second); 197 | w.show(); 198 | Config::set("multigrid", capabilities["ext_multigrid"]); 199 | Config::set("popupmenu", capabilities["ext_popupmenu"]); 200 | Config::set("cmdline", capabilities["ext_cmdline"]); 201 | return app.exec(); 202 | } 203 | catch(const std::exception& e) 204 | { 205 | QMessageBox::critical( 206 | nullptr, "NVUI Error", 207 | QString("nvui closed due to the following error: ") % e.what() % '.' 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/qt_editorui_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_QT_EDITORUI_BASE_HPP 2 | #define NVUI_QT_EDITORUI_BASE_HPP 3 | 4 | #include "editor_base.hpp" 5 | #include "mouse.hpp" 6 | #include "scalers.hpp" 7 | #include "types.hpp" 8 | #include 9 | 10 | class QKeyEvent; 11 | class QInputMethodEvent; 12 | class QMouseEvent; 13 | class QWheelEvent; 14 | class QDropEvent; 15 | class QResizeEvent; 16 | class QDragEnterEvent; 17 | 18 | struct UISignaller : public QObject 19 | { 20 | Q_OBJECT 21 | signals: 22 | void titlebar_set(bool set); 23 | void titlebar_toggled(); 24 | void frame_set(bool set); 25 | void frame_toggled(); 26 | void title_changed(QString title); 27 | void window_opacity_changed(double opacity); 28 | void fullscreen_set(bool fullscreen); 29 | void fullscreen_toggled(); 30 | void titlebar_font_family_set(QString family); 31 | void titlebar_font_size_set(double pointsize); 32 | void titlebar_colors_unset(); 33 | void titlebar_fg_set(QColor fg); 34 | void titlebar_bg_set(QColor bg); 35 | void titlebar_fg_bg_set(QColor fg, QColor bg); 36 | void default_colors_changed(QColor fg, QColor bg); 37 | void closed(); 38 | void editor_spawned( 39 | std::string nvim_path, 40 | std::unordered_map capabilities, 41 | std::vector args 42 | ); 43 | void editor_switched(std::size_t index); 44 | // Moving to next/prev editor 45 | void editor_changed_next(); 46 | void editor_changed_previous(); 47 | void cwd_changed(std::string dir); 48 | // Open a selection list to see and switch between currently open 49 | // editor instances. 50 | void editor_selection_list_opened(); 51 | }; 52 | 53 | // Does not inherit from a widget type, 54 | // so inheritors can inherit from 55 | // QtEditorUIBase and another QWidget-based type 56 | struct QtEditorUIBase : public EditorBase 57 | { 58 | using Base = EditorBase; 59 | public: 60 | QtEditorUIBase( 61 | QWidget& inheritor_instance, 62 | int cols, 63 | int rows, 64 | std::unordered_map capabilities, 65 | std::string nvim_path, 66 | std::vector nvim_args 67 | ); 68 | ~QtEditorUIBase() override = default; 69 | void attach(); 70 | void setup() override; 71 | virtual void set_animations_enabled(bool enabled); 72 | float charspacing() const; 73 | float linespacing() const; 74 | float move_animation_duration() const; 75 | int move_animation_frametime() const; 76 | float scroll_animation_duration() const; 77 | int scroll_animation_frametime() const; 78 | float cursor_animation_duration() const; 79 | int cursor_animation_frametime() const; 80 | bool animations_enabled() const; 81 | u32 snapshot_limit() const; 82 | // Connect to the UI signaller to receieve 83 | // signals 84 | // We have to do this since if this class 85 | // inherits from QObject there will be ambiguity 86 | // since QWidget inherits from QObject 87 | UISignaller* ui_signaller(); 88 | std::string current_dir() const; 89 | // Returns the inheriting widget 90 | QWidget* widget(); 91 | protected: 92 | bool idling() const; 93 | // Handling UI events. 94 | // NOTE: Does not handle hiding the mouse 95 | // and delegating key presses to other widgets, 96 | // that must be done by the widget's event handler 97 | void handle_key_press(QKeyEvent*); 98 | QVariant handle_ime_query(Qt::InputMethodQuery); 99 | void handle_ime_event(QInputMethodEvent*); 100 | void handle_nvim_resize(QResizeEvent*); 101 | void handle_mouse_press(QMouseEvent*); 102 | void handle_mouse_move(QMouseEvent*); 103 | void handle_mouse_release(QMouseEvent*); 104 | void handle_wheel(QWheelEvent*); 105 | void handle_drop(QDropEvent*); 106 | void handle_drag(QDragEnterEvent*); 107 | void handle_focuslost(QFocusEvent*); 108 | void handle_focusgained(QFocusEvent*); 109 | void listen_for_notification( 110 | std::string method, 111 | std::function func 112 | ); 113 | virtual void linespace_changed(float new_ls) = 0; 114 | virtual void charspace_changed(float new_cs) = 0; 115 | private: 116 | void spawn_editor_with_params(const Object& params); 117 | void cursor_moved() override; 118 | void typed(); 119 | void unhide_cursor(); 120 | void field_updated(std::string_view field, const Object& value) override; 121 | // Emits UISignaller::default_colors_changed 122 | void default_colors_changed(Color, Color) override; 123 | // Emits UISignaller::closed 124 | void do_close() override; 125 | struct GridPos 126 | { 127 | int grid_num; 128 | int row; 129 | int col; 130 | }; 131 | /** 132 | * Get the grid num, row, and column for the given 133 | * (x, y) pixel position. 134 | * Returns nullopt if no grid could be found that 135 | * matches the requirements. 136 | */ 137 | std::optional grid_pos_for(QPoint pos) const; 138 | void send_mouse_input( 139 | QPoint pos, 140 | std::string btn, 141 | std::string action, 142 | std::string mods 143 | ); 144 | void register_command_handlers(); 145 | void idle(); 146 | void un_idle(); 147 | void set_scaler(scalers::time_scaler& sc, const std::string& name); 148 | protected: 149 | QWidget& inheritor; 150 | struct AnimationDetails 151 | { 152 | void set_interval(int ms) 153 | { 154 | if (ms < 0) return; 155 | ms_interval = ms; 156 | } 157 | void set_duration(float dur) 158 | { 159 | if (dur < 0) return; 160 | duration = dur; 161 | } 162 | int ms_interval; 163 | float duration; 164 | }; 165 | struct IdleState 166 | { 167 | bool were_animations_enabled; 168 | }; 169 | struct UIInformation 170 | { 171 | int cols; 172 | int rows; 173 | std::unordered_map capabilities; 174 | }; 175 | UIInformation ui_attach_info; 176 | bool animate = true; 177 | bool mousehide = false; 178 | float charspace = 0; 179 | float linespace = 0; 180 | u32 snapshot_count = 4; 181 | AnimationDetails move_animation {4, 0.3f}; 182 | AnimationDetails scroll_animation {10, 0.3f}; 183 | AnimationDetails cursor_animation {10, 0.3f}; 184 | QTimer idle_timer {}; 185 | bool should_idle = false; 186 | std::optional idle_state; 187 | Mouse mouse; 188 | // This class is responsible for emitting signals 189 | // so that QtEditorUIBase doesn't have to inherit from QObject 190 | UISignaller signaller; 191 | private: 192 | std::string cwd; 193 | }; 194 | 195 | #endif // NVUI_QT_EDITORUI_BASE_HPP 196 | -------------------------------------------------------------------------------- /src/cursor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_CURSOR_HPP 2 | #define NVUI_CURSOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "hlstate.hpp" 11 | #include "object.hpp" 12 | #include "scalers.hpp" 13 | #include "animation.hpp" 14 | 15 | enum class CursorShape : std::uint8_t 16 | { 17 | Block, 18 | Horizontal, 19 | Vertical 20 | }; 21 | 22 | enum class CursorStatus : std::uint8_t 23 | { 24 | Visible, 25 | Hidden, 26 | Busy 27 | }; 28 | 29 | /// Stores the cursor's position, in terms of a grid. 30 | struct CursorPos 31 | { 32 | std::uint16_t grid_num; 33 | double grid_x; 34 | double grid_y; 35 | int row; 36 | int col; 37 | }; 38 | 39 | enum class CursorEffect 40 | { 41 | NoEffect, 42 | SmoothBlink, 43 | ExpandShrink, 44 | }; 45 | 46 | /// Describes the graphical properties of the cursor: 47 | /// The area it occupies (in terms of a single cell), as well as 48 | /// its highlight attribute, as well as whether the cell it contains 49 | /// needs to be redrawn (with hl_id). This is true for Block shape. 50 | struct CursorRect 51 | { 52 | QRectF rect {0, 0, 0, 0}; 53 | int hl_id = 0; 54 | bool should_draw_text = false; 55 | double opacity; 56 | }; 57 | 58 | /// Describes the information contained within a single mode of the cursor. 59 | struct ModeInfo 60 | { 61 | CursorShape cursor_shape = CursorShape::Block; 62 | int cell_percentage = 0; 63 | int blinkwait = 0; 64 | int blinkon = 0; 65 | int blinkoff = 0; 66 | int attr_id = 0; 67 | int attr_id_lm = 0; 68 | std::string short_name; 69 | std::string name; 70 | // (mouse_shape) 71 | }; 72 | 73 | class Nvim; 74 | 75 | /// The Cursor class stores the data for the Neovim cursor 76 | class Cursor : public QObject 77 | { 78 | Q_OBJECT 79 | public: 80 | static scalers::time_scaler animation_scaler; 81 | Cursor(); 82 | virtual void register_nvim(Nvim&); 83 | void set_animations_enabled(bool); 84 | bool animations_enabled() const; 85 | /** 86 | * Handles a 'mode_info_set' Neovim redraw event. 87 | */ 88 | void mode_info_set(std::span objs); 89 | /** 90 | * Handles a 'mode_change' Neovim redraw event. 91 | */ 92 | void mode_change(std::span objs); 93 | /** 94 | * Set the current position to pos, and refresh the cursor. 95 | */ 96 | void go_to(CursorPos pos); 97 | /** 98 | * Get the rectangle the cursor occupies at the current moment. 99 | * This in pixels, and is calculated using the given font width and font height. 100 | * If 'dbl' is true, the width of the cursor is doubled for the 'block' and 'underline' 101 | * cursor shapes. The vertical cursor shape remains the same. 102 | * If 'varheight' is false then the cursor rectangle does not incorporate 103 | * effects into the rectangle. This is for the cmdline 104 | */ 105 | std::optional rect( 106 | float font_width, 107 | float font_height, 108 | float scale = 1.0f, 109 | bool use_effects = true 110 | ) const noexcept; 111 | /** 112 | * Same as rect(), but based on the old cursor position and mode. 113 | */ 114 | std::optional old_rect(float font_width, float font_height) const noexcept; 115 | /** 116 | * Get the old position of the cursor (if it has an old position). 117 | */ 118 | inline std::optional old_pos() const noexcept 119 | { 120 | return prev_pos; 121 | } 122 | /** 123 | * Get the current position of the cursor (if it exists). 124 | */ 125 | inline std::optional pos() const noexcept 126 | { 127 | return cur_pos; 128 | } 129 | /** 130 | * Whether the cursor is hidden or not 131 | * If it's hidden, don't draw it 132 | */ 133 | inline bool hidden() const noexcept 134 | { 135 | return status == CursorStatus::Hidden || busy(); 136 | } 137 | /** 138 | * Hides the cursor, and disables all the timers so 139 | * that the cursor doesn't come back on again until 140 | * busy_stop 141 | */ 142 | void busy_start(); 143 | /** 144 | * Ends the Busy state, resetting the timers and 145 | * showing the cursor once again 146 | */ 147 | void busy_stop(); 148 | inline void set_caret_extend(float top = 0.f, float bottom = 0.f) 149 | { 150 | caret_extend_top = top; 151 | caret_extend_bottom = bottom; 152 | } 153 | inline void set_caret_extend_top(float top) 154 | { 155 | caret_extend_top = top; 156 | } 157 | inline void set_caret_extend_bottom(float bottom) 158 | { 159 | caret_extend_bottom = bottom; 160 | } 161 | int grid_num() const 162 | { 163 | if (!cur_pos) return -1; 164 | return cur_pos.value().grid_num; 165 | } 166 | void set_effect(std::string_view eff); 167 | void set_effect_anim_duration(double dur); 168 | void set_effect_anim_frametime(int ms); 169 | void set_effect_ease_func(std::string_view funcname); 170 | double opacity() const; 171 | private: 172 | float caret_extend_top = 0.f; 173 | float caret_extend_bottom = 0.f; 174 | int cell_width; 175 | int cell_height; 176 | CursorStatus status = CursorStatus::Visible; 177 | QTimer blinkwait_timer; 178 | QTimer blinkon_timer; 179 | QTimer blinkoff_timer; 180 | int row = 0; 181 | int col = 0; 182 | std::optional cur_pos; 183 | std::optional prev_pos; 184 | std::vector mode_info; 185 | ModeInfo cur_mode; 186 | std::size_t cur_mode_idx; 187 | std::size_t old_mode_idx; 188 | float old_mode_scale = 1.0f; 189 | std::string cur_mode_name; 190 | float cursor_animation_time; 191 | // These x and y are in terms of text, not pixels 192 | float cur_x = 0.f; 193 | float cur_y = 0.f; 194 | float old_x = 0.f; 195 | float old_y = 0.f; 196 | float destination_x = 0.f; 197 | float destination_y = 0.f; 198 | // Check the actual amount of time that passed between 199 | // each animation update 200 | Animation move_animation {}; 201 | Animation effect_animation {}; 202 | double opacity_level = 1.0; 203 | double height_level = 1.0; 204 | CursorEffect cursor_effect = CursorEffect::NoEffect; 205 | static scalers::time_scaler effect_ease_func; 206 | bool use_anims = true; 207 | signals: 208 | void anim_state_changed(); 209 | void cursor_visible(); 210 | void cursor_hidden(); 211 | private: 212 | void init_animations(); 213 | /** 214 | * Stop/restart the timers. 215 | * This should be activated after the mode 216 | * has changed. 217 | */ 218 | void reset_timers() noexcept; 219 | /** 220 | * Shows the cursor and sets the 'blinkon' timer, 221 | * which will start the 'blinkoff' timer when it 222 | * times out. 223 | */ 224 | void set_blinkon_timer(int ms) noexcept; 225 | /** 226 | * Hides the cursor and starts the blinkoff 227 | * timer, which will start the 'blinkon' timer 228 | * when it times out. 229 | */ 230 | void set_blinkoff_timer(int ms) noexcept; 231 | /** 232 | * Hide the cursor and set the status to Hidden. 233 | */ 234 | void hide() noexcept; 235 | /** 236 | * Show the cursor and set the status to Visible. 237 | */ 238 | void show() noexcept; 239 | /** 240 | * Whether the cursor is busy or not. 241 | */ 242 | inline bool busy() const noexcept 243 | { 244 | return status == CursorStatus::Busy; 245 | } 246 | bool use_animated_position() const; 247 | void animate_smoothblink(double percent_finished); 248 | void animate_expandshrink(double percent_finished); 249 | }; 250 | 251 | #endif // NVUI_CURSOR_HPP 252 | -------------------------------------------------------------------------------- /src/editor_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_EDITOR_BASE_HPP 2 | #define NVUI_EDITOR_BASE_HPP 3 | 4 | #include 5 | #include 6 | #include "cmdline.hpp" 7 | #include "cursor.hpp" 8 | #include "fontdesc.hpp" 9 | #include "grid.hpp" 10 | #include "hlstate.hpp" 11 | #include "object.hpp" 12 | #include "popupmenu.hpp" 13 | #include "nvim.hpp" 14 | 15 | /// UI Capabilities (Extensions) 16 | struct ExtensionCapabilities 17 | { 18 | bool linegrid = false; 19 | bool popupmenu = false; 20 | bool wildmenu = false; 21 | bool messages = false; 22 | bool cmdline = false; 23 | bool multigrid = false; 24 | }; 25 | 26 | // Base class for Editor UIs. 27 | // Doesn't assume any UI but uses Qt 28 | // for the event loop, and utility classes like QPoint, QSize, QRect 29 | // which are used in other classes like GridBase. 30 | struct EditorBase 31 | { 32 | public: 33 | struct NvimDimensions 34 | { 35 | int width; 36 | int height; 37 | }; 38 | using HandlerFunc = std::function)>; 39 | EditorBase( 40 | std::string nvim_path, 41 | std::vector nvim_args, 42 | QObject* thread_target_obj = qApp 43 | ); 44 | // Sets up the EditorBase to handle Neovim events. 45 | // This also creates instances of the cmdline and popup menu 46 | // throught the virtual popup_new() and cmdline_new(). 47 | // If this isn't called then redraw events won't be managed. 48 | virtual void setup(); 49 | Color default_bg() const; 50 | Color default_fg() const; 51 | bool mouse_enabled() const; 52 | void nvim_ui_attach( 53 | int width, int height, std::unordered_map capabilities 54 | ); 55 | const HLState& hlstate() const; 56 | FontDimensions font_dimensions() const; 57 | NvimDimensions nvim_dimensions() const; 58 | FontOpts default_font_weight() const; 59 | FontOpts default_font_style() const; 60 | // Send "confirm qa" signal to Neovim to close. 61 | void confirm_qa(); 62 | bool nvim_exited() const; 63 | virtual ~EditorBase(); 64 | private: 65 | /** 66 | * Handles a Neovim "grid_resize" event. 67 | */ 68 | void grid_resize(std::span objs); 69 | /** 70 | * Handles a Neovim "grid_line" event. 71 | */ 72 | void grid_line(std::span objs); 73 | /** 74 | * Paints the grid cursor at the given grid, row, and column. 75 | */ 76 | void grid_cursor_goto(std::span objs); 77 | /** 78 | * Handles a Neovim "option_set" event. 79 | */ 80 | void option_set(std::span objs); 81 | /** 82 | * Handles a Neovim "flush" event. 83 | * This paints the internal buffer onto the window. 84 | */ 85 | void flush(); 86 | /** 87 | * Handles a Neovim "win_pos" event. 88 | */ 89 | void win_pos(std::span objs); 90 | /** 91 | * Handles a Neovim "win_hide" event. 92 | */ 93 | void win_hide(std::span objs); 94 | /** 95 | * Handles a Neovim "win_float_pos" event. 96 | */ 97 | void win_float_pos(std::span objs); 98 | /** 99 | * Handles a Neovim "win_close" event. 100 | */ 101 | void win_close(std::span objs); 102 | /** 103 | * Handles a Neovim "win_viewport" event. 104 | */ 105 | void win_viewport(std::span objs); 106 | /// Handles a Neovim "grid_destroy" event. 107 | void grid_destroy(std::span objs); 108 | /// Handles a Neovim "msg_set_pos" event. 109 | void msg_set_pos(std::span objs); 110 | /** 111 | * Handles a Neovim "grid_clear" event 112 | */ 113 | void grid_clear(std::span objs); 114 | /** 115 | * Handles a Neovim "grid_scroll" event 116 | */ 117 | void grid_scroll(std::span objs); 118 | /** 119 | * Notify the editor area when resizing is enabled/disabled. 120 | */ 121 | void set_resizing(bool is_resizing); 122 | /** 123 | * Handles a "mode_info_set" Neovim redraw event. 124 | * Internally sends the data to neovim_cursor. 125 | */ 126 | void mode_info_set(std::span objs); 127 | /** 128 | * Handles a "mode_change" Neovim event. 129 | * Internally sends the data to neovim_cursor. 130 | */ 131 | void mode_change(std::span objs); 132 | /** 133 | * Handles a "busy_start" event, passing it to the Neovim cursor 134 | */ 135 | inline void busy_start() { n_cursor.busy_start(); } 136 | /** 137 | * Handles a "busy_stop" event, passing it to the Neovim cursor 138 | */ 139 | inline void busy_stop() { n_cursor.busy_stop(); } 140 | void cmdline_show(std::span objs); 141 | void cmdline_hide(std::span objs); 142 | void cmdline_cursor_pos(std::span objs); 143 | void cmdline_special_char(std::span objs); 144 | void cmdline_block_show(std::span objs); 145 | void cmdline_block_append(std::span objs); 146 | void cmdline_block_hide(std::span objs); 147 | void popupmenu_show(std::span objs); 148 | void popupmenu_hide(std::span objs); 149 | void popupmenu_select(std::span objs); 150 | void set_mouse_enabled(bool enabled); 151 | private: 152 | virtual void do_close() = 0; 153 | // Inheritors will control the actual type of popup menu being created. 154 | // But the returned value must not be null. 155 | virtual std::unique_ptr popup_new() = 0; 156 | virtual std::unique_ptr cmdline_new() = 0; 157 | virtual void cursor_moved() = 0; 158 | virtual void redraw() = 0; 159 | virtual void create_grid(u32 x, u32 y, u32 w, u32 h, u64 id); 160 | virtual void set_fonts(std::span list) = 0; 161 | virtual void default_colors_changed(Color fg, Color bg) = 0; 162 | // When an "option_set" field was updated. 163 | // Some things like ui capabilities ("ext_*") are handled by the base 164 | // class but things like linespace need to be handled by UI inheritors 165 | virtual void field_updated(std::string_view field, const Object& value); 166 | void register_handlers(); 167 | void handle_redraw(Object message); 168 | void order_grids(); 169 | std::unordered_map handlers; 170 | /** 171 | * Destroy the grid with the given grid_num. 172 | * If no grid exists with the given grid_num, 173 | * nothing happens. 174 | */ 175 | void destroy_grid(u64 grid_num); 176 | i64 get_win(const NeovimExt& ext) const; 177 | protected: 178 | void set_handler( 179 | std::string name, 180 | HandlerFunc func 181 | ); 182 | GridBase* find_grid(i64 grid_num); 183 | void send_redraw(); 184 | void screen_resized(int screenwidth, int screenheight); 185 | void set_font_dimensions(float width, float height); 186 | protected: 187 | HLState hl_state; 188 | Cursor n_cursor; 189 | std::unique_ptr popup_menu; 190 | std::unique_ptr cmdline; 191 | // This must get updated when changes are made to the dimensions 192 | // of the font 193 | std::vector> grids; 194 | std::vector guifonts; 195 | std::unique_ptr nvim; 196 | ExtensionCapabilities ext; 197 | bool grids_need_ordering = false; 198 | bool enable_mouse = false; 199 | bool done = false; 200 | FontDimensions ms_font_dimensions; 201 | std::string path_to_nvim; 202 | std::vector args_to_nvim; 203 | private: 204 | // Measures to prevent needless resizing requests 205 | QObject* target_object; 206 | QSize pixel_dimensions; 207 | QSize dimensions; 208 | std::optional queued_resize; 209 | bool resizing; 210 | FontOpts default_weight = FontOpts::Normal; 211 | FontOpts default_style = FontOpts::Normal; 212 | }; 213 | 214 | #endif // NVUI_EDITOR_BASE_HPP 215 | -------------------------------------------------------------------------------- /src/object.cpp: -------------------------------------------------------------------------------- 1 | #include "object.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | const Object Object::null = std::monostate {}; 10 | 11 | template struct overloaded : Ts... { using Ts::operator()...; }; 12 | template overloaded(Ts...) -> overloaded; 13 | 14 | void Object::to_stream(std::stringstream& ss) const 15 | { 16 | std::visit(overloaded { 17 | [&](const std::monostate&) { ss << "null"; }, 18 | [&](const std::string& s) { ss << '"' << s << '"'; }, 19 | [&](const int64_t& i) { ss << i; }, 20 | [&](const uint64_t& u) { ss << u; }, 21 | [&](const ObjectArray& v) { 22 | ss << '['; 23 | for(std::size_t i = 0; i < v.size() - 1 && i < v.size(); ++i) 24 | { 25 | v[i].to_stream(ss); 26 | ss << ", "; 27 | } 28 | if (!v.empty()) v.back().to_stream(ss); 29 | ss << ']'; 30 | }, 31 | [&](const ObjectMap& mp) { 32 | ss << '{'; 33 | for (const auto& [k, v] : mp) 34 | { 35 | ss << '"' << k << "\":"; 36 | v.to_stream(ss); 37 | ss << ", "; 38 | } 39 | ss << '}'; 40 | }, 41 | [&](const NeovimExt& ext) { 42 | Q_UNUSED(ext); 43 | ss << "EXT"; 44 | }, 45 | [&](const bool& b) { ss << b; }, 46 | [&](const double& d) { ss << d; }, 47 | [&](const Error& err) { 48 | ss << "Error: " << err.msg << '\n'; 49 | } 50 | }, v); 51 | } 52 | 53 | std::string Object::to_string() const noexcept 54 | { 55 | std::stringstream ss; 56 | ss << std::boolalpha; 57 | to_stream(ss); 58 | return ss.str(); 59 | } 60 | 61 | /// Visitor that satisfies msgpack's visitor requirements 62 | /// that parses messagepack data to an Object. 63 | /// The final object is stored in "result". 64 | /// To check if an error occurred during parsing, 65 | /// check the 'error' variable. If no error occurred, 66 | /// the error has value MsgpackVisitor::Error::None. 67 | struct MsgpackVisitor 68 | { 69 | enum Error 70 | { 71 | None, 72 | InsufficientBytesError, 73 | ParseError 74 | }; 75 | MsgpackVisitor(Object& o): result(o), stack() {} 76 | Object& result; 77 | Error error = Error::None; 78 | bool in_map = false; 79 | bool map_key_ended = false; 80 | Object* current = nullptr; 81 | std::stack> stack; 82 | const std::string* cur_key = nullptr; 83 | 84 | bool start_array(std::uint32_t len) 85 | { 86 | stack.push(current); 87 | auto* obj = place(ObjectArray()); 88 | assert(obj); 89 | obj->get().reserve(len); 90 | current = obj; 91 | return true; 92 | } 93 | 94 | bool start_map(std::uint32_t len) 95 | { 96 | stack.push(current); 97 | auto* obj = place(ObjectMap()); 98 | assert(obj); 99 | obj->get().reserve(len); 100 | current = obj; 101 | map_key_ended = false; // Key goes in 1st 102 | in_map = true; 103 | return true; 104 | } 105 | 106 | bool end_map() { pop_stack(); in_map = false; return true; } 107 | bool end_array() { pop_stack(); return true; } 108 | bool start_array_item() { return true; } 109 | bool end_array_item() { return true; } 110 | bool start_map_key() { map_key_ended = false; return true; } 111 | bool end_map_key() { map_key_ended = true; return true; } 112 | bool start_map_value() { return true; } 113 | bool end_map_value() { return true; } 114 | 115 | bool visit_nil() 116 | { 117 | place(std::monostate {}); 118 | return true; 119 | } 120 | 121 | bool visit_boolean(bool v) 122 | { 123 | place(v); 124 | return true; 125 | } 126 | 127 | bool visit_positive_integer(std::uint64_t v) 128 | { 129 | place(v); 130 | return true; 131 | } 132 | 133 | bool visit_negative_integer(std::int64_t v) 134 | { 135 | place(v); 136 | return true; 137 | } 138 | 139 | bool visit_float32(float v) 140 | { 141 | place(double(v)); 142 | return true; 143 | } 144 | 145 | bool visit_float64(double v) 146 | { 147 | place(v); 148 | return true; 149 | } 150 | 151 | bool visit_bin(const char* v, std::uint32_t size) 152 | { 153 | place(std::string(v, size)); 154 | return true; 155 | } 156 | 157 | bool visit_ext(const char* v, std::uint32_t size) 158 | { 159 | std::int8_t type = static_cast(*v); 160 | place(NeovimExt {type, QByteArray(v + 1, size - 1)}); 161 | return true; 162 | } 163 | 164 | bool visit_str(const char* v, std::uint32_t len) 165 | { 166 | place(std::string(v, len)); 167 | return true; 168 | } 169 | 170 | void parse_error(std::size_t, std::size_t) 171 | { 172 | // Log? 173 | error = ParseError; 174 | } 175 | 176 | void insufficient_bytes(std::size_t, std::size_t) 177 | { 178 | // Log? 179 | error = InsufficientBytesError; 180 | } 181 | 182 | private: 183 | 184 | void pop_stack() 185 | { 186 | if (stack.empty()) return; 187 | current = stack.top(); 188 | stack.pop(); 189 | } 190 | 191 | /// Place the arg where it should go and return a pointer to 192 | /// it (most of the time). 193 | template 194 | Object* place(T&& arg) 195 | { 196 | if (!current) 197 | { 198 | result = std::forward(arg); 199 | return &result; 200 | } 201 | else if (current->has()) 202 | { 203 | return ¤t->get().emplace_back(std::forward(arg)); 204 | } 205 | else if (current->has()) 206 | { 207 | auto& mp = current->get(); 208 | if (!cur_key) 209 | { 210 | if constexpr (std::is_same_v) 211 | { 212 | auto p = mp.emplace(std::forward(arg), Object()); 213 | cur_key = &p.first->first; 214 | return nullptr; 215 | } 216 | } 217 | else 218 | { 219 | assert(mp.contains(*cur_key)); 220 | Object* o = &mp[*cur_key]; 221 | *o = std::forward(arg); 222 | cur_key = nullptr; 223 | return o; 224 | } 225 | } 226 | return nullptr; 227 | } 228 | }; 229 | 230 | Object Object::from_msgpack(std::string_view sv, std::size_t& offset) noexcept 231 | { 232 | Object obj; 233 | MsgpackVisitor v {obj}; 234 | msgpack::parse(sv.data(), sv.size(), offset, v); 235 | switch(v.error) 236 | { 237 | case MsgpackVisitor::None: 238 | return obj; 239 | case MsgpackVisitor::InsufficientBytesError: 240 | return Error {"Insufficient Bytes"}; 241 | case MsgpackVisitor::ParseError: 242 | return Error {"Parse error"}; 243 | default: 244 | return Error {""}; 245 | } 246 | } 247 | 248 | Object Object::parse(const msgpack::object& obj) 249 | { 250 | Object o; 251 | MsgpackVisitor v {o}; 252 | msgpack::object_parser(obj).parse(v); 253 | return o; 254 | } 255 | 256 | std::size_t Object::children() const noexcept 257 | { 258 | if (auto* arr = array()) { return arr->size(); } 259 | else if (auto* mp = map()) { return mp->size(); } 260 | else return 0; 261 | } 262 | 263 | Object::~Object() 264 | { 265 | std::stack> stack; 266 | Object* cur = this; 267 | while(children() > 0) 268 | { 269 | if (cur->children() == 0) 270 | { 271 | cur = stack.top(); 272 | stack.pop(); 273 | } 274 | if (auto* arr = cur->array()) 275 | { 276 | auto* obj = &arr->back(); 277 | if (obj->children() > 0) 278 | { 279 | stack.push(cur); 280 | cur = obj; 281 | } 282 | else arr->pop_back(); 283 | } 284 | else if (auto* map = cur->map()) 285 | { 286 | // Keys are strings, no need to worry 287 | // Meanwhile values need to be checked 288 | auto it = map->end() - 1; 289 | auto* obj = &it->second; 290 | if (obj->children() > 0) 291 | { 292 | stack.push(cur); 293 | cur = obj; 294 | } 295 | else map->erase(map->end() - 1); 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/object.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NVUI_OBJECT_HPP 2 | #define NVUI_OBJECT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "utils.hpp" 17 | 18 | struct NeovimExt 19 | { 20 | std::int8_t type; 21 | QByteArray data; 22 | }; 23 | 24 | struct Error 25 | { 26 | std::string_view msg; // Constant message 27 | }; 28 | 29 | struct Object 30 | { 31 | static const Object null; 32 | using Map = boost::container::flat_map>; 33 | using Array = std::vector; 34 | using null_type = std::monostate; 35 | using string_type = std::string; 36 | using signed_type = std::int64_t; 37 | using unsigned_type = std::uint64_t; 38 | using array_type = Array; 39 | using map_type = Map; 40 | using bool_type = bool; 41 | using ext_type = NeovimExt; 42 | using float_type = double; 43 | using err_type = Error; 44 | using variant_type = std::variant< 45 | null_type, 46 | signed_type, 47 | unsigned_type, 48 | string_type, 49 | array_type, 50 | map_type, 51 | bool_type, 52 | ext_type, 53 | float_type, 54 | err_type 55 | >; 56 | template 57 | Object(T&& t): v(std::forward(t)) {} 58 | Object() = default; 59 | Object(Object&&) = default; 60 | Object(const Object&) = default; 61 | Object& operator=(const Object&) = default; 62 | Object& operator=(Object&&) = default; 63 | ~Object(); 64 | /// Parses an Object from a msgpack string. 65 | /// Note: This doesn't support the full messagepack specification, 66 | /// since map keys are always strings, but Neovim always specifies 67 | /// keys as strings, so it works. 68 | static Object from_msgpack(std::string_view sv, std::size_t& offset) noexcept; 69 | /// Parse from a msgpack::object from the msgpack-cpp library. 70 | /// If parsing directly from a messagepack-encoded string 71 | /// it's better to use Object::from_msgpack 72 | static Object parse(const msgpack::object&); 73 | std::string to_string() const noexcept; 74 | auto* array() noexcept { return std::get_if(&v); } 75 | auto* string() noexcept { return std::get_if(&v); } 76 | auto* i64() noexcept { return std::get_if(&v); } 77 | auto* u64() noexcept { return std::get_if(&v); } 78 | auto* map() noexcept { return std::get_if(&v); } 79 | auto* boolean() noexcept { return std::get_if(&v); } 80 | auto* f64() noexcept { return std::get_if(&v); } 81 | auto* ext() noexcept { return std::get_if(&v); } 82 | auto* err() noexcept { return std::get_if(&v); } 83 | // Const versions 84 | auto* array() const noexcept { return std::get_if(&v); } 85 | auto* string() const noexcept { return std::get_if(&v); } 86 | auto* i64() const noexcept { return std::get_if(&v); } 87 | auto* u64() const noexcept { return std::get_if(&v); } 88 | auto* map() const noexcept { return std::get_if(&v); } 89 | auto* boolean() const noexcept { return std::get_if(&v); } 90 | auto* f64() const noexcept { return std::get_if(&v); } 91 | auto* ext() const noexcept { return std::get_if(&v); } 92 | auto* err() const noexcept { return std::get_if(&v); } 93 | bool is_null() const noexcept { return has(); } 94 | bool is_err() const noexcept { return has(); } 95 | bool is_string() const noexcept { return has(); } 96 | bool is_array() const noexcept { return has(); } 97 | bool is_map() const noexcept { return has(); } 98 | bool is_signed() const { return has(); } 99 | bool is_unsigned() const { return has(); } 100 | bool is_float() const { return has(); } 101 | bool is_ext() const { return has(); } 102 | bool is_bool() const { return has(); } 103 | template 104 | bool has() const noexcept 105 | { 106 | return std::holds_alternative(v); 107 | } 108 | template 109 | explicit operator T() const 110 | { 111 | return std::visit([](const auto& val) -> T { 112 | if constexpr(std::is_convertible_v) 113 | { 114 | return T(val); 115 | } 116 | else 117 | { 118 | throw std::bad_variant_access(); 119 | } 120 | }, v); 121 | } 122 | 123 | template 124 | bool convertible() const noexcept 125 | { 126 | return std::visit([](const auto& val) -> bool { 127 | return std::is_convertible_v; 128 | }, v); 129 | } 130 | 131 | template 132 | T& get() 133 | { 134 | return std::get(v); 135 | } 136 | 137 | template 138 | const T& get() const 139 | { 140 | return std::get(v); 141 | } 142 | 143 | template 144 | std::optional try_convert() const noexcept 145 | { 146 | using type = std::optional; 147 | return std::visit([](const auto& arg) -> type { 148 | if constexpr(std::is_convertible_v) 149 | { 150 | return std::optional(arg); 151 | } 152 | return std::nullopt; 153 | }, v); 154 | } 155 | 156 | /// Decompose an Object to multiple values. 157 | /// The Object you call this on should be an array, 158 | /// otherwise std::nullopt will always be returned. 159 | /// If type conversion fails for any value, std::nullopt 160 | /// is returned. 161 | template 162 | std::optional> try_decompose() const noexcept 163 | { 164 | if (!has()) return std::nullopt; 165 | //using opt_tuple_type = std::optional>; 166 | const auto& arr = get(); 167 | std::tuple t; 168 | std::size_t idx = 0; 169 | bool valid = true; 170 | if (sizeof...(T) > arr.size()) return {}; 171 | for_each_in_tuple(t, [&](auto& elem) { 172 | using elem_type = std::remove_reference_t; 173 | auto v = arr.at(idx).try_convert(); 174 | if (!v) { valid = false; } 175 | else elem = *v; 176 | ++idx; 177 | }); 178 | if (valid) return t; 179 | return {}; 180 | } 181 | 182 | // Get key in map, or null if key doesn't exist / this is not a map. 183 | const Object& try_at(std::string_view s) const noexcept 184 | { 185 | if (!has()) return null; 186 | const auto& mp = get(); 187 | const auto it = mp.find(s); 188 | if (it == mp.cend()) return null; 189 | return it->second; 190 | } 191 | 192 | // Get value in index of array, or null if out of bounds / 193 | // this is not an array. 194 | const Object& try_at(std::size_t idx) const noexcept 195 | { 196 | if (!has()) return null; 197 | const auto& arr = get(); 198 | if (idx >= arr.size()) return null; 199 | return arr.at(idx); 200 | } 201 | 202 | // These will throw on bad access. 203 | const string_type& string_ref() const { return get(); } 204 | const bool_type& bool_ref() const { return get(); } 205 | const float_type& f64_ref() const { return get(); } 206 | const unsigned_type& u64_ref() const { return get(); } 207 | const signed_type& i64_ref() const { return get(); } 208 | const array_type& array_ref() const { return get(); } 209 | const map_type& map_ref() const { return get(); } 210 | const Error& err_ref() const { return get(); } 211 | const null_type& null_ref() const { return get(); } 212 | private: 213 | std::size_t children() const noexcept; 214 | void to_stream(std::stringstream& ss) const; 215 | variant_type v; 216 | }; 217 | 218 | using ObjectArray = Object::Array; 219 | using ObjectMap = Object::Map; 220 | 221 | #endif // NVUI_OBJECT_HPP 222 | -------------------------------------------------------------------------------- /src/qeditor.cpp: -------------------------------------------------------------------------------- 1 | #include "qeditor.hpp" 2 | #include "qpaintgrid.hpp" 3 | #include "font.hpp" 4 | #include 5 | #include 6 | 7 | /** 8 | * Sets relative's point size to a point size that is such that the horizontal 9 | * advance of the character 'a' is within tolerance of the target's horizontal 10 | * advance for the same character. 11 | * This is done using a binary search algorithm between 12 | * (0, target.pointSizeF() * 2.). The algorithm runs in a loop, the number 13 | * of times can be limited using max_iterations. If max_iterations is 0 14 | * the loop will run without stopping until it is within error. 15 | */ 16 | static void set_relative_font_size( 17 | const QFont& target, 18 | QFont& modified, 19 | const double tolerance, 20 | const std::size_t max_iterations 21 | ) 22 | { 23 | constexpr auto width = [](const QFontMetricsF& m) { 24 | return m.horizontalAdvance('a'); 25 | }; 26 | QFontMetricsF target_metrics {target}; 27 | const double target_width = width(target_metrics); 28 | double low = 0.; 29 | double high = target.pointSizeF() * 2.; 30 | modified.setPointSizeF(high); 31 | for(u32 rep = 0; 32 | (rep < max_iterations || max_iterations == 0) && low <= high; 33 | ++rep) 34 | { 35 | double mid = (low + high) / 2.; 36 | modified.setPointSizeF(mid); 37 | QFontMetricsF metrics {modified}; 38 | const double diff = target_width - width(metrics); 39 | if (std::abs(diff) <= tolerance) return; 40 | if (diff < 0) /** point size too big */ high = mid; 41 | else if (diff > 0) /** point size too low */ low = mid; 42 | else return; 43 | } 44 | } 45 | 46 | QEditor::QEditor( 47 | int cols, 48 | int rows, 49 | std::unordered_map capabilities, 50 | std::string nvim_path, 51 | std::vector nvim_args, 52 | QWidget* parent 53 | ) 54 | : QWidget(parent), 55 | QtEditorUIBase(*this, cols, rows, std::move(capabilities), 56 | std::move(nvim_path), std::move(nvim_args)) 57 | { 58 | first_font.setFamily(default_font_family()); 59 | first_font.setPointSizeF(11.25); 60 | setAttribute(Qt::WA_InputMethodEnabled); 61 | setAttribute(Qt::WA_OpaquePaintEvent); 62 | setAutoFillBackground(false); 63 | setAcceptDrops(true); 64 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 65 | setFocusPolicy(Qt::StrongFocus); 66 | setFocus(); 67 | setMouseTracking(true); 68 | } 69 | 70 | QEditor::~QEditor() = default; 71 | 72 | void QEditor::setup() 73 | { 74 | Base::setup(); 75 | } 76 | 77 | std::unique_ptr QEditor::popup_new() 78 | { 79 | return std::make_unique(&hl_state, this); 80 | } 81 | 82 | std::unique_ptr QEditor::cmdline_new() 83 | { 84 | return std::make_unique(hl_state, &n_cursor, this); 85 | } 86 | 87 | void QEditor::resizeEvent(QResizeEvent* ev) 88 | { 89 | Base::handle_nvim_resize(ev); 90 | update(); 91 | } 92 | 93 | void QEditor::mousePressEvent(QMouseEvent* ev) 94 | { 95 | Base::handle_mouse_press(ev); 96 | if (cursor() != Qt::ArrowCursor) 97 | { 98 | QWidget::mousePressEvent(ev); 99 | } 100 | } 101 | 102 | void QEditor::mouseReleaseEvent(QMouseEvent* ev) 103 | { 104 | QWidget::mouseReleaseEvent(ev); 105 | Base::handle_mouse_release(ev); 106 | } 107 | 108 | void QEditor::wheelEvent(QWheelEvent* ev) 109 | { 110 | Base::handle_wheel(ev); 111 | } 112 | 113 | void QEditor::dropEvent(QDropEvent* ev) 114 | { 115 | Base::handle_drop(ev); 116 | } 117 | 118 | void QEditor::dragEnterEvent(QDragEnterEvent* ev) 119 | { 120 | Base::handle_drag(ev); 121 | } 122 | 123 | void QEditor::inputMethodEvent(QInputMethodEvent* ev) 124 | { 125 | Base::handle_ime_event(ev); 126 | } 127 | 128 | void QEditor::mouseMoveEvent(QMouseEvent* ev) 129 | { 130 | Base::handle_mouse_move(ev); 131 | QWidget::mouseMoveEvent(ev); 132 | } 133 | 134 | void QEditor::keyPressEvent(QKeyEvent* ev) 135 | { 136 | QWidget::keyPressEvent(ev); 137 | Base::handle_key_press(ev); 138 | } 139 | 140 | void QEditor::redraw() { update(); } 141 | 142 | void QEditor::linespace_changed(float) 143 | { 144 | update_font_metrics(); 145 | } 146 | 147 | void QEditor::charspace_changed(float) 148 | { 149 | update_font_metrics(); 150 | } 151 | 152 | static void set_fontdesc(QFont& font, const FontDesc& fdesc) 153 | { 154 | const auto& [name, size, opts] = fdesc; 155 | if (size > 0.f) font.setPointSizeF(size); 156 | font::set_opts(font, opts); 157 | QString fm = name.empty() ? default_font_family() : QString::fromStdString(name); 158 | font.setFamily(fm); 159 | } 160 | 161 | void QEditor::set_fonts(std::span fontdescs) 162 | { 163 | if (fontdescs.empty()) return; 164 | fallback_indices.clear(); 165 | fonts.clear(); 166 | QFontDatabase font_db; 167 | set_fontdesc(first_font, fontdescs.front()); 168 | const auto validate_family = [&](QFont& f) { 169 | if (!font_db.hasFamily(f.family())) 170 | { 171 | nvim->err_write(fmt::format("No font named '{}' found.\n", 172 | f.family().toStdString() 173 | )); 174 | f.setFamily(default_font_family()); 175 | } 176 | }; 177 | validate_family(first_font); 178 | for(const auto& fontdesc : fontdescs) 179 | { 180 | QFont f; 181 | set_fontdesc(f, fontdesc); 182 | validate_family(f); 183 | set_relative_font_size(first_font, f, 0.0001, 1000); 184 | f.setWeight(qfont_weight(default_font_weight())); 185 | f.setStyle(qfont_style(default_font_style())); 186 | Font fo = f; 187 | fonts.push_back(std::move(f)); 188 | } 189 | update_font_metrics(); 190 | } 191 | 192 | void QEditor::update_font_metrics() 193 | { 194 | first_font.setLetterSpacing(QFont::AbsoluteSpacing, charspace); 195 | QFontMetricsF metrics {first_font}; 196 | float combined_height = std::max(metrics.height(), metrics.lineSpacing()); 197 | double font_height = combined_height + linespacing(); 198 | constexpr QChar any_char = 'W'; 199 | double font_width = metrics.horizontalAdvance(any_char) + charspace; 200 | for(auto& f : fonts) 201 | { 202 | QFont old_font = f.font(); 203 | old_font.setLetterSpacing(QFont::AbsoluteSpacing, charspace); 204 | f = old_font; 205 | } 206 | set_font_dimensions(font_width, font_height); 207 | // This is safe because we created an instance of PopupMenuQ 208 | // in the popup_new() function 209 | auto* popup = static_cast(popup_menu.get()); 210 | popup->font_changed(first_font, font_dimensions()); 211 | screen_resized(width(), height()); 212 | emit font_changed(); 213 | } 214 | 215 | void QEditor::create_grid(u32 x, u32 y, u32 w, u32 h, u64 id) 216 | { 217 | grids.push_back(std::make_unique(this, x, y, w, h, id)); 218 | } 219 | 220 | void QEditor::paintEvent(QPaintEvent*) 221 | { 222 | QPainter p(this); 223 | p.fillRect(rect(), hl_state.default_colors_get().bg().value_or(0).qcolor()); 224 | auto [cols, rows] = nvim_dimensions(); 225 | auto [font_width, font_height] = font_dimensions(); 226 | QRectF grid_clip_rect(0, 0, cols * font_width, rows * font_height); 227 | p.setClipRect(grid_clip_rect); 228 | p.setRenderHint(QPainter::SmoothPixmapTransform); 229 | for(auto& grid_base : grids) 230 | { 231 | auto* grid = static_cast(grid_base.get()); 232 | if (!grid->hidden) 233 | { 234 | QSize size = grid->buffer().size(); 235 | auto r = QRectF(grid->pos(), size).intersected(grid_clip_rect); 236 | p.setClipRect(r); 237 | grid->process_events(); 238 | grid->render(p); 239 | } 240 | } 241 | p.setClipRect(rect()); 242 | if (!n_cursor.hidden() && cmdline->hidden()) 243 | { 244 | auto* grid = find_grid(n_cursor.grid_num()); 245 | if (grid) static_cast(grid)->draw_cursor(p, n_cursor); 246 | } 247 | } 248 | 249 | u32 QEditor::font_for_ucs(u32 ucs) 250 | { 251 | if (ucs < 256) return 0; 252 | auto it = fallback_indices.find(ucs); 253 | if (it != fallback_indices.end()) return it->second; 254 | auto index = calc_fallback_index(ucs); 255 | fallback_indices[ucs] = index; 256 | return index; 257 | } 258 | 259 | u32 QEditor::calc_fallback_index(u32 ucs) 260 | { 261 | for(u32 i = 0; i < fonts.size(); ++i) 262 | { 263 | if (fonts[i].raw().supportsCharacter(ucs)) return i; 264 | } 265 | return 0; 266 | } 267 | 268 | void QEditor::focusInEvent(QFocusEvent* event) 269 | { 270 | Base::handle_focusgained(event); 271 | QWidget::focusInEvent(event); 272 | } 273 | 274 | void QEditor::focusOutEvent(QFocusEvent* event) 275 | { 276 | Base::handle_focuslost(event); 277 | QWidget::focusOutEvent(event); 278 | } 279 | -------------------------------------------------------------------------------- /src/titlebar.cpp: -------------------------------------------------------------------------------- 1 | #include "titlebar.hpp" 2 | #include "utils.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "constants.hpp" 22 | #include "window.hpp" 23 | 24 | constexpr int RATIO = 36; // I think the height of menu bar is 30px on 1080p screen 25 | 26 | /// MenuButton class is a wrapper around QPushButton 27 | /// that is made for titlebar buttons. This means that 28 | /// for example, if the button is at a corner of the screen, 29 | /// you are still able to resize the window without clicking the 30 | /// button. Of course, you can't move it. 31 | /// Also uses hover events to color the button background, since 32 | /// stylesheets didn't seem to be working (when the mouse wasn't pressed). 33 | class MenuButton : public QPushButton 34 | { 35 | public: 36 | MenuButton(QWidget* parent = nullptr, QColor hov = Qt::transparent) 37 | : QPushButton(parent), 38 | hov_bg(hov) 39 | { 40 | setFocusPolicy(Qt::NoFocus); 41 | setFlat(true); 42 | setAutoFillBackground(true); 43 | setMouseTracking(true); 44 | installEventFilter(this); 45 | } 46 | 47 | MenuButton(TitleBar* parent, QColor hov = Qt::transparent) 48 | : MenuButton(static_cast(nullptr), hov) 49 | { 50 | resize_move_handler = [parent](QPointF p) { emit parent->resize_move(p); }; 51 | } 52 | 53 | void set_hov_bg(const QColor& bg) 54 | { 55 | hov_bg = bg; 56 | } 57 | 58 | // We shouldn't need to use this (can just change the parent widget), 59 | // but it's here 60 | void set_default_bg(const QColor& bg) 61 | { 62 | default_bg = bg; 63 | } 64 | 65 | signals: 66 | void resize_move(QPointF pt); 67 | private: 68 | std::function resize_move_handler = [](auto){}; 69 | QColor hov_bg; 70 | QColor default_bg = Qt::transparent; 71 | QColor cur_bg = default_bg; 72 | 73 | // At least on my system, the stylesheet's :hover property 74 | // doesn't detect hovers while the mouse isn't pressed. 75 | // The MenuButton solves this problem. 76 | // We manually check for hovers / mouse presses and set the color 77 | void hover_move(QHoverEvent* event) 78 | { 79 | Q_UNUSED(event); 80 | if (cursor() == Qt::ArrowCursor) 81 | { 82 | cur_bg = hov_bg; 83 | } 84 | else 85 | { 86 | cur_bg = default_bg; 87 | } 88 | update(); 89 | } 90 | 91 | void hover_enter(QHoverEvent* event) 92 | { 93 | Q_UNUSED(event); 94 | if (cursor() == Qt::ArrowCursor) 95 | { 96 | cur_bg = hov_bg; 97 | } 98 | update(); 99 | } 100 | 101 | void hover_leave(QHoverEvent* event) 102 | { 103 | Q_UNUSED(event); 104 | cur_bg = default_bg; 105 | update(); 106 | } 107 | 108 | void mouse_pressed(QMouseEvent* event) 109 | { 110 | Q_UNUSED(event); 111 | if (cursor() != Qt::ArrowCursor) 112 | { 113 | resize_move_handler(event->windowPos()); 114 | } 115 | else 116 | { 117 | QPushButton::mousePressEvent(event); 118 | } 119 | } 120 | 121 | protected: 122 | bool eventFilter(QObject* watched, QEvent* event) override 123 | { 124 | event->accept(); 125 | Q_UNUSED(watched); 126 | switch(event->type()) 127 | { 128 | case QEvent::Paint: 129 | { 130 | paintEvent(static_cast(event)); 131 | return true; 132 | } 133 | case QEvent::HoverMove: 134 | { 135 | hover_move(static_cast(event)); 136 | return true; 137 | } 138 | case QEvent::HoverEnter: 139 | { 140 | hover_enter(static_cast(event)); 141 | return true; 142 | } 143 | case QEvent::HoverLeave: 144 | { 145 | hover_leave(static_cast(event)); 146 | return true; 147 | } 148 | case QEvent::MouseButtonPress: 149 | { 150 | mouse_pressed(static_cast(event)); 151 | return true; 152 | } 153 | default: 154 | { 155 | QPushButton::event(event); 156 | return true; 157 | } 158 | } 159 | return false; 160 | } 161 | void paintEvent(QPaintEvent* event) override 162 | { 163 | QPainter painter(this); 164 | painter.fillRect(rect(), cur_bg); 165 | QPushButton::paintEvent(event); 166 | } 167 | }; 168 | 169 | /** 170 | TitleBar implementation 171 | */ 172 | 173 | // Hover colors of min, max, close buttons 174 | static const QColor mm_light = "#665c74"; 175 | static const QColor mm_dark = "#3d4148"; 176 | static const QColor close_bg = {255, 0, 0}; // completely red 177 | 178 | TitleBar::TitleBar(QString text, Window* window) 179 | : maximized(false), 180 | foreground("#ffffff"), 181 | background("#282c34"), 182 | win(window) 183 | { 184 | setMouseTracking(true); 185 | assert(qApp->screens().size() > 0); 186 | const int menu_height = qApp->screens()[0]->size().height() / RATIO; 187 | const int menu_width = (menu_height * 3) / 2; 188 | title_font.setPointSizeF(9.5); 189 | title_font.setHintingPreference(QFont::HintingPreference::PreferVerticalHinting); 190 | close_icon = get_close_icon(); 191 | max_icon = get_maximize_icon(); 192 | min_icon = get_minimize_icon(); 193 | close_btn = new MenuButton(this, close_bg); 194 | max_btn = new MenuButton(this, mm_dark); 195 | min_btn = new MenuButton(this, mm_dark); 196 | close_btn->setIcon(close_icon); 197 | max_btn->setIcon(max_icon); 198 | min_btn->setIcon(min_icon); 199 | layout = new QHBoxLayout(); 200 | QPushButton* appicon = new MenuButton(); 201 | appicon->setIcon(QIcon(constants::appicon())); 202 | label = new QLabel(text); 203 | label->setMouseTracking(true); 204 | label->setFont(title_font); 205 | // Window buttons on left for non-Windows 206 | #if !defined(Q_OS_WIN) 207 | layout->addWidget(close_btn); 208 | layout->addWidget(min_btn); 209 | layout->addWidget(max_btn); 210 | #else 211 | layout->addWidget(appicon); 212 | layout->addSpacerItem(new QSpacerItem(2 * menu_width, menu_height)); 213 | #endif 214 | layout->addStretch(); 215 | layout->setMargin(0); 216 | layout->addWidget(label); 217 | // Create Icons (when we maximize the window we will update max_btn so it's a little special) 218 | layout->addStretch(); 219 | // If only we could set mouse tracking to true by default... 220 | #if defined(Q_OS_WIN) 221 | layout->addWidget(min_btn); 222 | layout->addWidget(max_btn); 223 | layout->addWidget(close_btn); 224 | #else 225 | layout->addSpacerItem(new QSpacerItem(3 * menu_width, menu_height)); 226 | #endif 227 | titlebar_widget = new QWidget(); 228 | titlebar_widget->setLayout(layout); 229 | titlebar_widget->setStyleSheet("background-color: " % background.name() % "; color: " % foreground.name() % ";"); 230 | win->setMenuWidget(titlebar_widget); 231 | const QSize size {menu_width, menu_height}; 232 | close_btn->setFixedSize(size); 233 | min_btn->setFixedSize(size); 234 | max_btn->setFixedSize(size); 235 | titlebar_widget->setFocusPolicy(Qt::NoFocus); 236 | setFocusPolicy(Qt::NoFocus); 237 | titlebar_widget->setMouseTracking(true); 238 | QObject::connect(close_btn, SIGNAL(clicked()), win, SLOT(close())); 239 | QObject::connect(min_btn, SIGNAL(clicked()), win, SLOT(showMinimized())); 240 | QObject::connect(max_btn, SIGNAL(clicked()), this, SLOT(minimize_maximize())); 241 | } 242 | 243 | void TitleBar::set_title_text(const QString& text) 244 | { 245 | label->setText(text); 246 | win->setWindowTitle(text); 247 | } 248 | 249 | bool is_light(const QColor& color) 250 | { 251 | return color.lightness() >= 127; 252 | } 253 | 254 | void TitleBar::update_titlebar() 255 | { 256 | close_icon = get_close_icon(); 257 | min_icon = get_minimize_icon(); 258 | close_btn->setIcon(close_icon); 259 | min_btn->setIcon(min_icon); 260 | update_maxicon(); 261 | const QString ss = "background: " % background.name() % "; color: " % foreground.name() % ";"; 262 | titlebar_widget->setStyleSheet(ss); 263 | // Check the "mode" of the background color 264 | if (is_light(background)) 265 | { 266 | min_btn->set_hov_bg(mm_light); 267 | max_btn->set_hov_bg(mm_light); 268 | } 269 | else 270 | { 271 | min_btn->set_hov_bg(mm_dark); 272 | max_btn->set_hov_bg(mm_dark); 273 | } 274 | } 275 | 276 | void TitleBar::update_maxicon() 277 | { 278 | if (win->isMaximized()) 279 | { 280 | max_icon = get_maximized_icon(); 281 | max_btn->setIcon(max_icon); 282 | } 283 | else 284 | { 285 | max_icon = get_maximize_icon(); 286 | max_btn->setIcon(max_icon); 287 | } 288 | } 289 | 290 | void TitleBar::set_color(QColor color, bool is_foreground) 291 | { 292 | if (is_foreground) 293 | { 294 | foreground = std::move(color); 295 | } 296 | else 297 | { 298 | background = std::move(color); 299 | } 300 | update_titlebar(); 301 | } 302 | 303 | void TitleBar::set_color(QColor fg, QColor bg) 304 | { 305 | foreground = std::move(fg); 306 | background = std::move(bg); 307 | update_titlebar(); 308 | } 309 | 310 | void TitleBar::minimize_maximize() 311 | { 312 | if (win->isMaximized()) 313 | { 314 | win->showNormal(); 315 | } 316 | else 317 | { 318 | win->maximize(); 319 | } 320 | update_maxicon(); 321 | } 322 | 323 | void TitleBar::colors_changed(QColor fg, QColor bg) 324 | { 325 | set_color(fg, bg); 326 | } 327 | 328 | void TitleBar::win_state_changed(Qt::WindowStates) 329 | { 330 | update_maxicon(); 331 | } 332 | -------------------------------------------------------------------------------- /src/nvim.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define BOOST_PROCESS_WINDOWS_USE_NAMED_PIPE 4 | #include "nvim.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "object.hpp" 19 | 20 | #ifdef _WIN32 21 | #include 22 | #endif 23 | 24 | namespace bp = boost::process; 25 | 26 | using Lock = std::lock_guard; 27 | 28 | static boost::filesystem::path 29 | get_nvim_path(const std::string& path) 30 | { 31 | if (path.empty()) 32 | { 33 | return bp::search_path("nvim"); 34 | } 35 | return boost::filesystem::path(path); 36 | } 37 | 38 | Nvim::Nvim(std::string path, std::vector args) 39 | : notification_handlers(), 40 | request_handlers(), 41 | closed(false), 42 | num_responses(0), 43 | current_msgid(0), 44 | proc_group(), 45 | stdout_pipe(), 46 | stdin_pipe(), 47 | error() 48 | { 49 | auto nvim_path = get_nvim_path(path); 50 | if (nvim_path.empty()) 51 | { 52 | throw std::runtime_error("Neovim not found in PATH"); 53 | } 54 | nvim = bp::child( 55 | nvim_path, 56 | args, 57 | bp::std_out > stdout_pipe, 58 | bp::std_in < stdin_pipe, 59 | bp::std_err > error, 60 | proc_group 61 | #ifdef _WIN32 62 | , bp::windows::create_no_window 63 | #endif 64 | ); 65 | err_reader = std::thread(std::bind(&Nvim::read_error_sync, this)); 66 | out_reader = std::thread(std::bind(&Nvim::read_output_sync, this)); 67 | } 68 | 69 | Object Nvim::get_api_info() 70 | { 71 | return send_blocking_request("nvim_get_api_info", std::array {}); 72 | } 73 | 74 | template 75 | Object Nvim::send_blocking_request(const std::string& method, T&& params) 76 | { 77 | std::mutex m; 78 | std::unique_lock lock {m}; 79 | std::condition_variable cv; 80 | Object result; 81 | send_request_cb(method, std::forward(params), [&](Object res, Object err) { 82 | if (!err.is_null()) result = Object::null; 83 | else result = std::move(res); 84 | cv.notify_one(); 85 | }); 86 | cv.wait(lock); 87 | return result; 88 | } 89 | 90 | void Nvim::resize(const int new_width, const int new_height) 91 | { 92 | send_notification("nvim_ui_try_resize", std::make_tuple(new_width, new_height)); 93 | } 94 | 95 | // Although this is synchronous, it will be performed on another thread. 96 | void Nvim::read_output_sync() 97 | { 98 | constexpr int buffer_maxsize = 1024 * 1024; 99 | auto buffer = std::make_unique(buffer_maxsize); 100 | char* buf = buffer.get(); 101 | while(!closed && running()) 102 | { 103 | using std::size_t; 104 | auto msg_size = static_cast(stdout_pipe.read(buf, buffer_maxsize)); 105 | if (!msg_size) continue; 106 | std::string_view sv {buf, msg_size}; 107 | std::size_t offset = 0; 108 | while(offset < msg_size) 109 | { 110 | auto parsed = Object::from_msgpack(sv, offset); 111 | auto* arr = parsed.array(); 112 | if (!(arr && (arr->size() == 3 || arr->size() == 4))) continue; 113 | const auto msg_type = arr->at(0).u64(); 114 | if (!msg_type) continue; 115 | switch(*msg_type) 116 | { 117 | case Type::Notification: 118 | { 119 | assert(arr->size() == 3); 120 | const auto method_str = arr->at(1).string(); 121 | if (!method_str) continue; 122 | notification_handlers_mutex.lock(); 123 | const auto func_it = notification_handlers.find(*method_str); 124 | if (func_it == notification_handlers.end()) 125 | { 126 | notification_handlers_mutex.unlock(); 127 | } 128 | else 129 | { 130 | const auto func = func_it->second; 131 | notification_handlers_mutex.unlock(); 132 | func(std::move(parsed)); 133 | } 134 | break; 135 | } 136 | case Type::Request: 137 | { 138 | assert(arr->size() == 4); 139 | const auto* method_str = arr->at(2).string(); 140 | if (!method_str) continue; 141 | request_handlers_mutex.lock(); 142 | const auto func_it = request_handlers.find(*method_str); 143 | if (func_it == request_handlers.end()) 144 | { 145 | request_handlers_mutex.unlock(); 146 | } 147 | else 148 | { 149 | const auto func = func_it->second; 150 | request_handlers_mutex.unlock(); 151 | func(std::move(parsed)); 152 | } 153 | break; 154 | } 155 | case Type::Response: 156 | { 157 | assert(arr->size() == 4); 158 | const auto msgid = arr->at(1).u64(); 159 | assert(msgid); 160 | if (!msgid) continue; 161 | response_cb_mutex.lock(); 162 | const auto cb_it = singleshot_callbacks.find(*msgid); 163 | if (cb_it != singleshot_callbacks.end()) 164 | { 165 | const auto cb = cb_it->second; 166 | response_cb_mutex.unlock(); 167 | cb(std::move(arr->at(3)), std::move(arr->at(2))); 168 | } 169 | else response_cb_mutex.unlock(); 170 | break; 171 | } 172 | default: 173 | qWarning() << "Received an invalid msgpack message type: " << *msg_type << '\n'; 174 | continue; 175 | } 176 | } 177 | } 178 | did_exit = true; 179 | // Exiting. When Nvim closes both the error and output pipe close, 180 | // but we don't want to call the exit handler twice. 181 | // Make sure we're not adding an exit handler at the same time we're 182 | // calling it 183 | Lock exit_lock {exit_handler_mutex}; 184 | on_exit_handler(); 185 | } 186 | 187 | void Nvim::attach_ui(const int rows, const int cols, std::unordered_map capabilities) 188 | { 189 | send_notification("nvim_ui_attach", std::make_tuple(rows, cols, std::move(capabilities))); 190 | } 191 | void Nvim::read_error_sync() 192 | { 193 | // 500KB should be enough for stderr (not receving any huge input) 194 | constexpr int buffer_maxsize = 512 * 1024; 195 | auto buffer = std::make_unique(buffer_maxsize); 196 | std::uint32_t bytes_read; 197 | bp::pipe& err_pipe = error.pipe(); 198 | while(!closed && running()) 199 | { 200 | bytes_read = err_pipe.read(buffer.get(), buffer_maxsize); 201 | if (bytes_read) 202 | { 203 | std::string s(buffer.get(), bytes_read); 204 | std::cout << "Error occurred: " << s << '\n'; 205 | } 206 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 207 | } 208 | } 209 | 210 | std::vector Nvim::default_args() 211 | { 212 | return {"--embed"}; 213 | } 214 | 215 | int Nvim::exit_code() 216 | { 217 | if (nvim.running()) 218 | { 219 | return INT_MIN; 220 | } 221 | return nvim.exit_code(); 222 | } 223 | 224 | 225 | bool Nvim::running() 226 | { 227 | return nvim.running(); 228 | } 229 | 230 | void Nvim::set_notification_handler( 231 | const std::string& method, 232 | msgpack_callback handler 233 | ) 234 | { 235 | Lock lock {notification_handlers_mutex}; 236 | notification_handlers.emplace(method, handler); 237 | } 238 | 239 | void Nvim::set_request_handler( 240 | const std::string& method, 241 | msgpack_callback handler 242 | ) 243 | { 244 | Lock lock {request_handlers_mutex}; 245 | request_handlers.emplace(method, handler); 246 | } 247 | 248 | 249 | void Nvim::command(const std::string& cmd) 250 | { 251 | send_request("nvim_command", std::make_tuple(cmd)); 252 | } 253 | 254 | void Nvim::send_input( 255 | const bool c, 256 | const bool s, 257 | const bool a, 258 | const bool d, 259 | const std::string& key, 260 | bool is_special 261 | ) 262 | { 263 | std::string input_string; 264 | if (c || s || a || d || is_special) 265 | { 266 | const std::string_view first = c ? "C-" : ""; 267 | const std::string_view second = s ? "S-" : ""; 268 | const std::string_view third = a ? "M-" : ""; 269 | const std::string_view fourth = d ? "D-" : ""; 270 | input_string = fmt::format("<{}{}{}{}{}>", first, second, third, fourth, key); 271 | } 272 | else 273 | { 274 | input_string = key; 275 | } 276 | send_input(input_string); 277 | } 278 | 279 | void Nvim::send_input(std::string key) 280 | { 281 | send_notification("nvim_input", std::array {std::move(key)}); 282 | } 283 | 284 | void Nvim::on_exit(std::function handler) 285 | { 286 | Lock lock {exit_handler_mutex}; 287 | on_exit_handler = std::move(handler); 288 | } 289 | 290 | void Nvim::resize_cb(const int width, const int height, response_cb cb) 291 | { 292 | send_request_cb("nvim_ui_try_resize", std::make_tuple(width, height), std::move(cb)); 293 | } 294 | 295 | void Nvim::eval_cb(const std::string& expr, response_cb cb) 296 | { 297 | send_request_cb("nvim_eval", std::make_tuple(expr), std::move(cb)); 298 | } 299 | 300 | void Nvim::exec_viml( 301 | const std::string& str, 302 | bool capture_output, 303 | std::optional cb 304 | ) 305 | { 306 | if (cb) 307 | { 308 | send_request_cb("nvim_exec", std::make_tuple(str, capture_output), *cb); 309 | } 310 | else 311 | { 312 | send_notification("nvim_exec", std::make_tuple(str, capture_output)); 313 | } 314 | } 315 | 316 | void Nvim::input_mouse( 317 | std::string button, 318 | std::string action, 319 | std::string modifiers, 320 | int grid, 321 | int row, 322 | int col 323 | ) 324 | { 325 | send_notification("nvim_input_mouse", std::tuple { 326 | std::move(button), std::move(action), std::move(modifiers), 327 | grid, row, col 328 | }); 329 | } 330 | 331 | void Nvim::set_client_info(const ClientInfo& info) 332 | { 333 | send_notification("nvim_set_client_info", info); 334 | } 335 | 336 | Nvim::~Nvim() 337 | { 338 | // Close I/O Pipes and terminate process 339 | closed = true; 340 | nvim.terminate(); 341 | error.pipe().close(); 342 | stdout_pipe.close(); 343 | stdin_pipe.close(); 344 | out_reader.join(); 345 | err_reader.join(); 346 | } 347 | -------------------------------------------------------------------------------- /src/input.cpp: -------------------------------------------------------------------------------- 1 | #include "input.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | std::string event_to_string(const QKeyEvent* event, bool* special) 12 | { 13 | *special = true; 14 | switch(event->key()) 15 | { 16 | case Qt::Key_Enter: 17 | return "CR"; 18 | case Qt::Key_Return: 19 | return "CR"; 20 | case Qt::Key_Backspace: 21 | return "BS"; 22 | case Qt::Key_Tab: 23 | return "Tab"; 24 | case Qt::Key_Backtab: 25 | return "Tab"; 26 | case Qt::Key_Down: 27 | return "Down"; 28 | case Qt::Key_Up: 29 | return "Up"; 30 | case Qt::Key_Left: 31 | return "Left"; 32 | case Qt::Key_Right: 33 | return "Right"; 34 | case Qt::Key_Escape: 35 | return "Esc"; 36 | case Qt::Key_Home: 37 | return "Home"; 38 | case Qt::Key_End: 39 | return "End"; 40 | case Qt::Key_Insert: 41 | return "Insert"; 42 | case Qt::Key_Delete: 43 | return "Del"; 44 | case Qt::Key_PageUp: 45 | return "PageUp"; 46 | case Qt::Key_PageDown: 47 | return "PageDown"; 48 | case Qt::Key_Less: 49 | return "LT"; 50 | case Qt::Key_Space: 51 | return "Space"; 52 | case Qt::Key_F1: 53 | return "F1"; 54 | case Qt::Key_F2: 55 | return "F2"; 56 | case Qt::Key_F3: 57 | return "F3"; 58 | case Qt::Key_F4: 59 | return "F4"; 60 | case Qt::Key_F5: 61 | return "F5"; 62 | case Qt::Key_F6: 63 | return "F6"; 64 | case Qt::Key_F7: 65 | return "F7"; 66 | case Qt::Key_F8: 67 | return "F8"; 68 | case Qt::Key_F9: 69 | return "F9"; 70 | case Qt::Key_F10: 71 | return "F10"; 72 | case Qt::Key_F11: 73 | return "F11"; 74 | case Qt::Key_F12: 75 | return "F12"; 76 | case Qt::Key_F13: 77 | return "F13"; 78 | case Qt::Key_F14: 79 | return "F14"; 80 | case Qt::Key_F15: 81 | return "F15"; 82 | case Qt::Key_F16: 83 | return "F16"; 84 | case Qt::Key_F17: 85 | return "F17"; 86 | case Qt::Key_F18: 87 | return "F18"; 88 | case Qt::Key_F19: 89 | return "F19"; 90 | case Qt::Key_F20: 91 | return "F20"; 92 | default: 93 | *special = false; 94 | return event->text().toStdString(); 95 | } 96 | } 97 | 98 | [[maybe_unused]] 99 | static bool is_modifier(int key) 100 | { 101 | switch(key) 102 | { 103 | case Qt::Key_Meta: 104 | case Qt::Key_Control: 105 | case Qt::Key_Alt: 106 | case Qt::Key_AltGr: 107 | case Qt::Key_Shift: 108 | case Qt::Key_Super_L: 109 | case Qt::Key_Super_R: 110 | case Qt::Key_CapsLock: 111 | return true; 112 | default: 113 | return false; 114 | } 115 | return false; 116 | } 117 | 118 | /// Taken from Neovim-Qt's "IsAsciiCharRequiringAlt" function in input.cpp. 119 | [[maybe_unused]] 120 | static bool requires_alt(int key, Qt::KeyboardModifiers mods, QChar c) 121 | { 122 | // Ignore all key events where Alt is not pressed 123 | if (!(mods & Qt::AltModifier)) return false; 124 | 125 | // These low-ascii characters may require AltModifier on MacOS 126 | if ((c == '[' && key != Qt::Key_BracketLeft) 127 | || (c == ']' && key != Qt::Key_BracketRight) 128 | || (c == '{' && key != Qt::Key_BraceLeft) 129 | || (c == '}' && key != Qt::Key_BraceRight) 130 | || (c == '|' && key != Qt::Key_Bar) 131 | || (c == '~' && key != Qt::Key_AsciiTilde) 132 | || (c == '@' && key != Qt::Key_At)) 133 | { 134 | return true; 135 | } 136 | 137 | return false; 138 | } 139 | 140 | /// Normalizes a key event. Does nothing for non-Mac platforms. 141 | /// Taken from Neovim-Qt's "CreatePlatformNormalizedKeyEvent" 142 | /// function for each platform. 143 | static QKeyEvent normalize_key_event( 144 | QEvent::Type type, 145 | int key, 146 | Qt::KeyboardModifiers mods, 147 | const QString& text 148 | ) 149 | { 150 | #if !defined(Q_OS_MAC) 151 | return {type, key, mods, text}; 152 | #else 153 | if (text.isEmpty()) 154 | { 155 | return {type, key, mods, text}; 156 | } 157 | 158 | const QChar& c = text.at(0); 159 | if ((c.unicode() >= 0x80 && c.isPrint()) || requires_alt(key, mods, c)) 160 | { 161 | mods &= ~Qt::AltModifier; 162 | } 163 | return {type, key, mods, text}; 164 | #endif // !defined(Q_OS_MAC) 165 | } 166 | 167 | /// Taken from Neovim-Qt's Input::ControlModifier() 168 | static constexpr auto c_mod() 169 | { 170 | #if defined(Q_OS_WIN) 171 | return Qt::ControlModifier; 172 | #elif defined(Q_OS_MAC) 173 | return Qt::MetaModifier; 174 | #else 175 | return Qt::ControlModifier; 176 | #endif 177 | } 178 | 179 | /// Taken from NeovimQt's Input::CmdModifier() 180 | static constexpr auto d_mod() 181 | { 182 | #if defined(Q_OS_WIN) 183 | return Qt::NoModifier; 184 | #elif defined(Q_OS_MAC) 185 | return Qt::ControlModifier; 186 | #else 187 | return Qt::MetaModifier; 188 | #endif 189 | } 190 | 191 | 192 | static std::string mod_prefix(Qt::KeyboardModifiers mods) 193 | { 194 | std::string result; 195 | if (mods & Qt::ShiftModifier) result.append("S-"); 196 | if (mods & c_mod()) result.append("C-"); 197 | if (mods & Qt::AltModifier) result.append("M-"); 198 | if (mods & d_mod()) result.append("D-"); 199 | return result; 200 | } 201 | 202 | /// Equivalent of Neovim's "ToKeyString", 203 | /// but returning an std::string. 204 | static std::string key_mod_str( 205 | Qt::KeyboardModifiers mods, 206 | std::string_view text 207 | ) 208 | { 209 | return fmt::format("<{}{}>", mod_prefix(mods), text); 210 | } 211 | 212 | /// Derived from Neovim-Qt's NeovimQt::Input::convertKey 213 | /// method, see 214 | /// https://github.com/equalsraf/neovim-qt/blob/master/src/gui/input.cpp#L144 215 | static std::string convertKey(const QKeyEvent& ev) noexcept 216 | { 217 | bool x = requires_alt(0, Qt::NoModifier, 0); 218 | Q_UNUSED(x); 219 | QString text = ev.text(); 220 | auto mod = ev.modifiers(); 221 | int key = ev.key(); 222 | static const std::unordered_map keypadKeys { 223 | { Qt::Key_Home, "kHome" }, 224 | { Qt::Key_End, "kEnd" }, 225 | { Qt::Key_PageUp, "kPageUp" }, 226 | { Qt::Key_PageDown, "kPageDown" }, 227 | { Qt::Key_Plus, "kPlus" }, 228 | { Qt::Key_Minus, "kMinus" }, 229 | { Qt::Key_multiply, "kMultiply" }, 230 | { Qt::Key_division, "kDivide" }, 231 | { Qt::Key_Enter, "kEnter" }, 232 | { Qt::Key_Period, "kPoint" }, 233 | { Qt::Key_0, "k0" }, 234 | { Qt::Key_1, "k1" }, 235 | { Qt::Key_2, "k2" }, 236 | { Qt::Key_3, "k3" }, 237 | { Qt::Key_4, "k4" }, 238 | { Qt::Key_5, "k5" }, 239 | { Qt::Key_6, "k6" }, 240 | { Qt::Key_7, "k7" }, 241 | { Qt::Key_8, "k8" }, 242 | { Qt::Key_9, "k9" }, 243 | }; 244 | 245 | #ifdef Q_OS_WIN 246 | /// Windows sends Ctrl+Alt when AltGr is pressed, 247 | /// but the text already factors in AltGr. Solution: Ignore Ctrl and Alt 248 | if ((mod & Qt::ControlModifier) && (mod & Qt::AltModifier)) 249 | { 250 | mod &= ~Qt::ControlModifier; 251 | mod &= ~Qt::AltModifier; 252 | } 253 | #endif // Q_OS_WIN 254 | 255 | if (mod & Qt::KeypadModifier && keypadKeys.contains(key)) 256 | { 257 | return fmt::format("<{}{}>", mod_prefix(mod), keypadKeys.at(key)); 258 | } 259 | 260 | // Issue#917: On Linux, Control + Space sends text as "\u0000" 261 | if (key == Qt::Key_Space && text.size() > 0 && !text.at(0).isPrint()) 262 | { 263 | text = " "; 264 | } 265 | 266 | bool is_special = false; 267 | auto s = event_to_string(&ev, &is_special); 268 | if (is_special) 269 | { 270 | if (key == Qt::Key_Space 271 | || key == Qt::Key_Backspace) 272 | { 273 | mod &= ~Qt::ShiftModifier; 274 | } 275 | return key_mod_str(mod, s); 276 | } 277 | 278 | // Issue#864: Some international layouts insert accents (~^`) on Key_Space 279 | if (key == Qt::Key_Space && !text.isEmpty() && text != " ") 280 | { 281 | if (mod != Qt::NoModifier) 282 | { 283 | return key_mod_str(mod, text.toStdString()); 284 | } 285 | 286 | return text.toStdString(); 287 | } 288 | 289 | 290 | // The key "<" should be sent as "" 291 | // Issue#607: Remove ShiftModifier from "<", shift is implied 292 | if (text == "<") 293 | { 294 | const Qt::KeyboardModifiers modNoShift = mod & ~Qt::KeyboardModifier::ShiftModifier; 295 | return key_mod_str(modNoShift, "LT"); 296 | } 297 | 298 | // Issue#170: Normalize modifiers, CTRL+^ always sends as 299 | const bool isCaretKey = (key == Qt::Key_6 || key == Qt::Key_AsciiCircum); 300 | if (isCaretKey && mod & c_mod()) 301 | { 302 | const Qt::KeyboardModifiers modNoShiftMeta = mod 303 | & Qt::KeyboardModifier::ShiftModifier 304 | & ~d_mod(); 305 | return key_mod_str(modNoShiftMeta, "^"); 306 | } 307 | 308 | if (text == "\\") 309 | { 310 | return key_mod_str(mod, "Bslash"); 311 | } 312 | 313 | if (text.isEmpty()) 314 | { 315 | // Ignore all modifier-only key events. 316 | // Issue#344: Ignore Ctrl-Shift, C-S- being treated as C-Space 317 | // Issue#593: Pressing Control + Super inserts ^S 318 | // Issue#199: Pressing Control + CapsLock inserts $ 319 | if (is_modifier(key)) return {}; 320 | 321 | // Ignore special keys 322 | // Issue#671: `q`/`p`/`r` key is sent by Mute/Volume DOWN/Volume UP 323 | if (key == Qt::Key::Key_VolumeDown 324 | || key == Qt::Key::Key_VolumeMute 325 | || key == Qt::Key::Key_VolumeUp) return {}; 326 | 327 | text = QChar(key); 328 | if (!(mod & Qt::ShiftModifier)) text = text.toLower(); 329 | } 330 | 331 | const QChar c = text.at(0); 332 | 333 | // Remove Shift, skip when ALT or CTRL are pressed 334 | if ((c.unicode() >= 0x80 || c.isPrint()) 335 | && !(mod & c_mod()) && !(mod & d_mod())) 336 | { 337 | mod &= ~Qt::ShiftModifier; 338 | } 339 | 340 | // Ignore empty characters at the start of the ASCII range 341 | if (c.unicode() < 0x20) 342 | { 343 | text = QChar(key); 344 | if (!(mod & Qt::ShiftModifier)) text = text.toLower(); 345 | } 346 | 347 | // Perform any platform specific QKeyEvent modifications 348 | auto evNormalized = normalize_key_event(ev.type(), key, mod, text); 349 | 350 | const auto prefix = mod_prefix(evNormalized.modifiers()); 351 | if (!prefix.empty()) 352 | { 353 | auto normalized_mods = evNormalized.modifiers(); 354 | return key_mod_str(normalized_mods, evNormalized.text().toStdString()); 355 | } 356 | 357 | return evNormalized.text().toStdString(); 358 | } 359 | 360 | std::string convert_key(const QKeyEvent& ev) 361 | { 362 | return convertKey(ev); 363 | } 364 | --------------------------------------------------------------------------------