diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37349309be..3a6ae2d507 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,14 +9,14 @@ on: workflow_dispatch: # Always run on pull requests pull_request: - # And on master when manually pushed or after merges + # And on `main` when manually pushed or after merges paths-ignore: - "website/**" - ".vscode/**" - "**.md" push: branches: - main + - main paths-ignore: - "website/**" - ".vscode/**" @@ -34,7 +34,6 @@ jobs: uses: ./.github/workflows/lint-app.yml test: - needs: lint strategy: fail-fast: false matrix: @@ -46,24 +45,13 @@ jobs: - uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - components: clippy - name: Show toolchain info run: cargo --version --verbose - - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install neovim uses: rhysd/action-setup-vim@v1 with: @@ -75,6 +63,8 @@ jobs: - name: Install cargo-nextest run: cargo binstall -y cargo-nextest + - uses: Swatinem/rust-cache@v2 + - name: Test env: RUST_BACKTRACE: full @@ -93,24 +83,31 @@ jobs: path: | test-results-${{ matrix.os }}-${{ matrix.toolchain }} + clippy: + strategy: + fail-fast: false + matrix: + os: [windows-latest, macOS-12, ubuntu-latest] + toolchain: [stable, nightly] + runs-on: ${{ matrix.os }} - - name: Install Sarif - run: cargo binstall -y clippy-sarif sarif-fmt + steps: + - uses: actions/checkout@v4 - - name: Setup Reviewdog - uses: reviewdog/action-setup@v1 + - name: Setup toolchain + uses: dtolnay/rust-toolchain@master with: - reviewdog_version: latest + toolchain: ${{ matrix.toolchain }} + components: clippy - - name: Run Clippy - run: cargo clippy --all-targets --message-format=json | clippy-sarif | tee clippy.sarif | sarif-fmt - continue-on-error: true + - uses: Swatinem/rust-cache@v2 - - name: Run Clippy Reviewdog - run: cat clippy.sarif | reviewdog -f=sarif -name=clippy -reporter=github-check -filter-mode=file -fail-on-error=true -level=warning - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Show toolchain info + run: cargo --version --verbose + + - name: Run Clippy + run: cargo clippy --all-targets -- -D warnings + continue-on-error: ${{ matrix.toolchain == 'nightly' }} event-upload: needs: test @@ -123,7 +120,6 @@ jobs: path: ${{ github.event_path }} build-deploy: - needs: test strategy: fail-fast: false matrix: @@ -134,25 +130,23 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/cache@v4 + - name: Setup toolchain + uses: dtolnay/rust-toolchain@master with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + toolchain: stable + + - name: Install Cargo Binstall + uses: cargo-bins/cargo-binstall@main - name: Install dependencies run: | if [[ $RUNNER_OS == "Windows" ]]; then - if ! which cargo-wix; then cargo install cargo-wix; fi + if ! which cargo-wix; then cargo binstall -y cargo-wix; fi elif [[ $RUNNER_OS == "macOS" ]]; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" && brew install create-dmg rustup target add x86_64-apple-darwin rustup target add aarch64-apple-darwin - if ! which cargo-bundle; then cargo install cargo-bundle; fi elif [[ $RUNNER_OS == "Linux" ]]; then sudo apt-get update @@ -168,6 +162,8 @@ jobs: with: neovim: true + - uses: Swatinem/rust-cache@v2 + - name: Build run: | if [[ $RUNNER_OS == "Windows" ]]; then @@ -177,10 +173,8 @@ jobs: echo "MACOSX_DEPLOYMENT_TARGET=10.11" >> $GITHUB_ENV # x86 cargo build --locked --release --target=x86_64-apple-darwin - cargo bundle --release --target=x86_64-apple-darwin # arch cargo build --locked --release --target=aarch64-apple-darwin - cargo bundle --release --target=aarch64-apple-darwin elif [[ $RUNNER_OS == "Linux" ]]; then cargo build --locked --release @@ -188,31 +182,23 @@ jobs: - name: Prepare Artifacts run: | - cd target/release - if [[ $RUNNER_OS == "Windows" ]]; then echo "ARTIFACT=neovide.exe" >> $GITHUB_ENV echo "ARTIFACT2=neovide.msi" >> $GITHUB_ENV elif [[ $RUNNER_OS == "macOS" ]]; then - # merge builds - mkdir -p bundle/osx - rm -rf bundle/osx/neovide.app || true - cp -R ../x86_64-apple-darwin/release/bundle/osx/neovide.app \ - bundle/osx/neovide.app - rm bundle/osx/neovide.app/Contents/MacOS/neovide - lipo ../x86_64-apple-darwin/release/bundle/osx/neovide.app/Contents/MacOS/neovide \ - ../aarch64-apple-darwin/release/bundle/osx/neovide.app/Contents/MacOS/neovide \ - -create -output \ - bundle/osx/neovide.app/Contents/MacOS/neovide - codesign --force --deep -s - bundle/osx/neovide.app - # create .dmg - hdiutil create neovide-uncompressed.dmg -volname "neovide" -srcfolder bundle/osx - hdiutil convert neovide-uncompressed.dmg -format UDZO -o neovide.dmg - - echo "ARTIFACT=neovide.dmg" >> $GITHUB_ENV + cd $GITHUB_WORKSPACE + # create .dmg for x86_64-apple-darwin + ./macos-builder/run x86_64-apple-darwin + + # create .dmg for aarch64-apple-darwin + ./macos-builder/run aarch64-apple-darwin + + echo "ARTIFACT4=Neovide-x86_64-apple-darwin.dmg" >> $GITHUB_ENV + echo "ARTIFACT5=Neovide-aarch64-apple-darwin.dmg" >> $GITHUB_ENV elif [[ $RUNNER_OS == "Linux" ]]; then + cd target/release # archive artifact strip neovide tar czvf neovide-linux-x86_64.tar.gz neovide @@ -231,13 +217,13 @@ jobs: --icon-file=../../assets/neovide.svg \ --output=appimage - echo "ARTIFACT=neovide-linux-x86_64.tar.gz" >> $GITHUB_ENV echo "ARTIFACT2=neovide.AppImage" >> $GITHUB_ENV echo "ARTIFACT3=neovide.AppImage.zsync" >> $GITHUB_ENV fi - - uses: actions/upload-artifact@v4 + - if: env.ARTIFACT + uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT }} path: target/release/${{ env.ARTIFACT }} @@ -253,3 +239,15 @@ jobs: with: name: ${{ env.ARTIFACT3 }} path: target/release/${{ env.ARTIFACT3 }} + + - if: env.ARTIFACT4 + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARTIFACT4 }} + path: target/x86_64-apple-darwin/release/bundle/osx/${{ env.ARTIFACT4 }} + + - if: env.ARTIFACT5 + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARTIFACT5 }} + path: target/aarch64-apple-darwin/release/bundle/osx/${{ env.ARTIFACT5 }} diff --git a/Cargo.lock b/Cargo.lock index 3a8e93ac8a..609022f0d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,7 +196,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -235,7 +235,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -245,7 +245,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.70", + "syn 2.0.71", "which 4.4.2", ] @@ -309,7 +309,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -330,9 +330,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "calloop" @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907d8581360765417f8f2e0e7d602733bbed60156b4465b7617243689ef9b83d" +checksum = "18e2d530f35b40a84124146478cd16f34225306a8441998836466a2e2961c950" dependencies = [ "jobserver", "libc", @@ -461,7 +461,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -492,7 +492,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bytes 1.6.0", + "bytes 1.6.1", "memchr", ] @@ -627,7 +627,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -773,7 +773,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -853,7 +853,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -1184,6 +1184,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1427,7 +1436,7 @@ dependencies = [ [[package]] name = "neovide" -version = "0.13.0" +version = "0.13.2" dependencies = [ "anyhow", "approx", @@ -1447,7 +1456,7 @@ dependencies = [ "icrate", "image", "indoc", - "itertools", + "itertools 0.13.0", "lazy_static", "log", "lru", @@ -1644,7 +1653,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -1833,7 +1842,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -1910,7 +1919,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2239,7 +2248,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2284,7 +2293,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2316,9 +2325,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "skia-bindings" -version = "0.73.0" +version = "0.75.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43555ec5d87ee8c14273f3045bf09504a58c6afe842c51d1593b62abf095aa96" +checksum = "c06e19e97660b09a381c6eb566849b63556b1a90b8e2c6ba2d146b3f5066847b" dependencies = [ "bindgen", "cc", @@ -2333,9 +2342,9 @@ dependencies = [ [[package]] name = "skia-safe" -version = "0.73.0" +version = "0.75.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431cc5c1ca6008199bf2e849083dd05539c9f2a2ffc1c24b521e6e0ef2c31a7" +checksum = "ad6e6f369522471b585c99427720b53aad04016fa4314e0a8cf23f17083a4e4c" dependencies = [ "bitflags 2.6.0", "lazy_static", @@ -2463,7 +2472,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2488,9 +2497,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.70" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -2525,7 +2534,7 @@ checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2591,7 +2600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", - "bytes 1.6.0", + "bytes 1.6.1", "libc", "mio", "num_cpus", @@ -2622,7 +2631,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -2631,7 +2640,7 @@ version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ - "bytes 1.6.0", + "bytes 1.6.1", "futures-core", "futures-io", "futures-sink", @@ -2797,7 +2806,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -2831,7 +2840,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3053,7 +3062,7 @@ checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ "windows-implement", "windows-interface", - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -3065,7 +3074,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] @@ -3076,16 +3085,17 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] [[package]] name = "windows-registry" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acc134c90a0318d873ec962b13149e9c862ff0d2669082a709a4810167a3c6ee" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-result", + "windows-result 0.2.0", + "windows-strings", "windows-targets 0.52.6", ] @@ -3098,6 +3108,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3502,5 +3531,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.71", ] diff --git a/Cargo.toml b/Cargo.toml index 3c9cd3f054..83b04f1358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "neovide" -version = "0.13.0" +version = "0.13.2" edition = "2021" build = "build.rs" resolver = "2" @@ -44,7 +44,7 @@ glutin = "0.31.1" glutin-winit = "0.4.2" image = { version = "0.25.0", default-features = false, features = ["ico"] } indoc = "2.0.5" -itertools = "0.12.1" +itertools = "0.13.0" lazy_static = "1.4.0" log = "0.4.16" lru = "0.12.2" @@ -86,7 +86,7 @@ serial_test = "3.0.0" [target.'cfg(target_os = "windows")'.dependencies] wslpath-rs = "0.1" -skia-safe = { version = "0.73.0", features = ["gl", "d3d", "textlayout"] } +skia-safe = { version = "0.75.0", features = ["gl", "d3d", "textlayout"] } windows = { version = "0.56.0", features = [ "Win32_Graphics_Direct3D", "Win32_Graphics_Direct3D12", @@ -95,14 +95,15 @@ windows = { version = "0.56.0", features = [ "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", "Win32_Security", + "Win32_System_Console", "Win32_System_Performance", "Win32_System_Threading", "Win32_UI_HiDpi", ] } -windows-registry = "0.1.1" +windows-registry = "0.2.0" [target.'cfg(not(target_os = "windows"))'.dependencies] -skia-safe = { version = "0.73.0", features = ["gl", "textlayout"] } +skia-safe = { version = "0.75.0", features = ["gl", "textlayout"] } [target.'cfg(target_os = "macos")'.dependencies] icrate = { version = "0.0.4", features = [ @@ -140,9 +141,9 @@ debug = true name = "Neovide" identifier = "com.neovide.neovide" icon = ["assets/neovide.ico"] -version = "0.13.0" +version = "0.13.2" resources = [] -copyright = "Copyright (c) Neovide Contributors 2023. All rights reserved." +copyright = "Copyright (c) Neovide Contributors 2024. All rights reserved." category = "Productivity" short_description = "A simple GUI for Neovim." long_description = """ diff --git a/lua/init.lua b/lua/init.lua index 48aa4c02b3..ab9697c73d 100644 --- a/lua/init.lua +++ b/lua/init.lua @@ -1,5 +1,6 @@ ---@class Args ---@field neovide_channel_id integer +---@field neovide_version string ---@field register_clipboard boolean ---@field register_right_click boolean ---@field enable_focus_command boolean @@ -11,6 +12,7 @@ local args = ... vim.g.neovide_channel_id = args.neovide_channel_id +vim.g.neovide_version = args.neovide_version -- Set some basic rendering options. vim.o.lazyredraw = false @@ -25,15 +27,15 @@ local function rpcrequest(method, ...) end local function set_clipboard(register) - return function(lines, regtype) - rpcrequest("neovide.set_clipboard", lines) + return function(lines) + rpcrequest("neovide.set_clipboard", lines, register) end end local function get_clipboard(register) return function() return rpcrequest("neovide.get_clipboard", register) - end + end end if args.register_clipboard and not vim.g.neovide_no_custom_clipboard then @@ -47,10 +49,14 @@ if args.register_clipboard and not vim.g.neovide_no_custom_clipboard then ["+"] = get_clipboard("+"), ["*"] = get_clipboard("*"), }, - cache_enabled = 0 + cache_enabled = false } + vim.g.loaded_clipboard_provider = nil + vim.cmd.runtime("autoload/provider/clipboard.vim") end + + if args.register_right_click then vim.api.nvim_create_user_command("NeovideRegisterRightClick", function() rpcnotify("neovide.register_right_click") diff --git a/macos-builder/run b/macos-builder/run index fa5155bafb..d13e6e4dad 100755 --- a/macos-builder/run +++ b/macos-builder/run @@ -5,7 +5,7 @@ set -e TARGET="neovide" EXTRAS_DIR="extra" ASSETS_DIR="assets" -RELEASE_DIR="target/release" +RELEASE_DIR="target/$1/release" BUNDLE_DIR="${RELEASE_DIR}/bundle" APP_NAME="Neovide.app" @@ -15,7 +15,7 @@ APP_BINARY="${RELEASE_DIR}/${TARGET}" APP_BINARY_DIR="${APP_DIR}/${APP_NAME}/Contents/MacOS" APP_EXTRAS_DIR="${APP_DIR}/${APP_NAME}/Contents/Resources" -DMG_NAME="Neovide.dmg" +DMG_NAME="Neovide-$1.dmg" DMG_VOLNAME="Neovide" DMG_FILESYSTEM="HFS+" DMG_FORMAT="UDZO" diff --git a/src/bridge/api_info.rs b/src/bridge/api_info.rs index d82434dd74..2d06e636cd 100644 --- a/src/bridge/api_info.rs +++ b/src/bridge/api_info.rs @@ -16,7 +16,7 @@ impl fmt::Display for ApiInfoParseError { impl<'a> From> for ApiInfoParseError { fn from(value: ValueRef) -> Self { - Self(format!("{}", value)) + Self(format!("{value}")) } } diff --git a/src/bridge/clipboard.rs b/src/bridge/clipboard.rs index 832e196234..efc5e3ad01 100644 --- a/src/bridge/clipboard.rs +++ b/src/bridge/clipboard.rs @@ -4,8 +4,12 @@ use rmpv::Value; use crate::clipboard; -pub fn get_clipboard_contents(format: Option<&str>) -> Result> { - let clipboard_raw = clipboard::get_contents()?.replace('\r', ""); +pub fn get_clipboard_contents( + format: Option<&str>, + register: &Value, +) -> Result> { + let register = register.as_str().unwrap_or("+"); + let clipboard_raw = clipboard::get_contents(register)?.replace('\r', ""); let is_line_paste = clipboard_raw.ends_with('\n'); let lines = if let Some("dos") = format { @@ -29,11 +33,15 @@ pub fn get_clipboard_contents(format: Option<&str>) -> Result Result> { +pub fn set_clipboard_contents( + value: &Value, + register: &Value, +) -> Result> { #[cfg(not(windows))] let endline = "\n"; #[cfg(windows)] let endline = "\r\n"; + let register = register.as_str().unwrap_or("+"); let lines = value .as_array() @@ -46,7 +54,7 @@ pub fn set_clipboard_contents(value: &Value) -> Result (String, Vec) { use crate::error_handling::ResultPanicExplanation; + // If $TERM is set, we assume user is running from a terminal, and we shouldn't + // re-initialize the environment. + // See https://github.com/neovide/neovide/issues/2584 + if env::var_os("TERM").is_some() { + return ( + command.to_string(), + args.iter().map(|s| s.to_string()).collect(), + ); + } + let user = env::var("USER").unwrap_or_explained_panic("USER environment variable not found"); let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string()); diff --git a/src/bridge/events.rs b/src/bridge/events.rs index abb7f49664..2eeeda8beb 100644 --- a/src/bridge/events.rs +++ b/src/bridge/events.rs @@ -209,7 +209,7 @@ pub enum RedrawEvent { anchor_column: f64, #[allow(unused)] focusable: bool, - sort_order: Option, + z_index: u64, }, #[allow(unused)] WindowExternalPosition { @@ -677,8 +677,8 @@ fn parse_window_anchor(value: Value) -> Result { } fn parse_win_float_pos(win_float_pos_arguments: Vec) -> Result { - let ([grid, _window, anchor, anchor_grid, anchor_row, anchor_column, focusable], [sort_order]) = - extract_values_with_optional(win_float_pos_arguments)?; + let [grid, _window, anchor, anchor_grid, anchor_row, anchor_column, focusable, z_index] = + extract_values(win_float_pos_arguments)?; Ok(RedrawEvent::WindowFloatPosition { grid: parse_u64(grid)?, @@ -687,7 +687,7 @@ fn parse_win_float_pos(win_float_pos_arguments: Vec) -> Result set_clipboard_contents(&arguments[0]) + "neovide.set_clipboard" => set_clipboard_contents(&arguments[0], &arguments[1]) .map_err(|_| Value::from("cannot set clipboard contents")), "neovide.quit" => { let error_code = arguments[0] diff --git a/src/bridge/setup.rs b/src/bridge/setup.rs index 6da3110f9b..e8fba7b7b6 100644 --- a/src/bridge/setup.rs +++ b/src/bridge/setup.rs @@ -46,6 +46,10 @@ pub async fn setup_neovide_specific_state( Value::from("minor"), Value::from(env!("CARGO_PKG_VERSION_MINOR")), ), + ( + Value::from("patch"), + Value::from(env!("CARGO_PKG_VERSION_PATCH")), + ), ], "ui", vec![], @@ -78,6 +82,10 @@ pub async fn setup_neovide_specific_state( Value::from("neovide_channel_id"), Value::from(api_information.channel), ), + ( + Value::from("neovide_version"), + Value::from(crate_version!()), + ), ( Value::from("register_clipboard"), Value::from(register_clipboard), diff --git a/src/bridge/ui_commands.rs b/src/bridge/ui_commands.rs index 2a4fe565d9..a2f4bce5b1 100644 --- a/src/bridge/ui_commands.rs +++ b/src/bridge/ui_commands.rs @@ -7,9 +7,10 @@ use nvim_rs::{call_args, error::CallError, rpc::model::IntoVal, Neovim, Value}; use strum::AsRefStr; use tokio::sync::mpsc::unbounded_channel; -use super::show_error_message; +use super::{show_error_message, SETTINGS}; use crate::{ bridge::NeovimWriter, + cmd_line::CmdLineSettings, profiling::{tracy_dynamic_zone, tracy_fiber_enter, tracy_fiber_leave}, LoggingSender, }; @@ -178,14 +179,19 @@ impl ParallelCommand { // for failure is when neovim has already quit, and a command, for example mouse move is // being sent let result = match self { - ParallelCommand::Quit => nvim - .command( - "if get(g:, 'neovide_confirm_quit', 0) == 1 | confirm qa | else | qa! | endif", - ) - .await + ParallelCommand::Quit => { // Ignore all errors, since neovim exits immediately before the response is sent. // We could an RPC notify instead of request, but nvim-rs does currently not support it. - .or(Ok(())), + let _ = nvim + .exec_lua( + include_str!("exit_handler.lua"), + vec![Value::Boolean( + SETTINGS.get::().server.is_some(), + )], + ) + .await; + Ok(()) + } ParallelCommand::Resize { width, height } => nvim .ui_try_resize(width.max(10) as i64, height.max(3) as i64) .await @@ -199,7 +205,13 @@ impl ParallelCommand { ParallelCommand::FileDrop(path) => nvim .cmd( vec![ - ("cmd".into(), "tabnew".into()), + ( + "cmd".into(), + (SETTINGS.get::().tabs) + .then(|| "tabnew".to_string()) + .unwrap_or("edit".into()) + .into(), + ), ("magic".into(), vec![("file".into(), false.into())].into()), ("args".into(), vec![Value::from(path)].into()), ], @@ -209,7 +221,7 @@ impl ParallelCommand { .map(|_| ()) // We don't care about the result .context("FileDrop failed"), ParallelCommand::SetBackground(background) => nvim - .command(format!("set background={}", background).as_str()) + .command(format!("set background={background}").as_str()) .await .context("SetBackground failed"), ParallelCommand::DisplayAvailableFonts(fonts) => display_available_fonts(nvim, fonts) diff --git a/src/clipboard.rs b/src/clipboard.rs index 1518e94e96..ce85b0cd1e 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -2,7 +2,10 @@ use std::error::Error; use std::sync::OnceLock; #[cfg(target_os = "linux")] -use copypasta::wayland_clipboard; +use copypasta::{ + wayland_clipboard, + x11_clipboard::{Primary as X11SelectionClipboard, X11ClipboardContext}, +}; use copypasta::{ClipboardContext, ClipboardProvider}; use parking_lot::Mutex; use raw_window_handle::HasRawDisplayHandle; @@ -14,24 +17,61 @@ use crate::window::UserEvent; type Result = std::result::Result>; -static CLIPBOARD: OnceLock>> = OnceLock::new(); +pub struct Clipboard { + clipboard: Box, + #[cfg(target_os = "linux")] + selection: Box, +} + +static CLIPBOARD: OnceLock> = OnceLock::new(); pub fn init(event_loop: &EventLoop) { CLIPBOARD .set(Mutex::new(match event_loop.raw_display_handle() { #[cfg(target_os = "linux")] RawDisplayHandle::Wayland(WaylandDisplayHandle { display, .. }) => unsafe { - Box::new(wayland_clipboard::create_clipboards_from_external(display).1) + let (selection, clipboard) = + wayland_clipboard::create_clipboards_from_external(display); + Clipboard { + clipboard: Box::new(clipboard), + selection: Box::new(selection), + } + }, + #[cfg(target_os = "linux")] + _ => Clipboard { + clipboard: Box::new(ClipboardContext::new().unwrap()), + selection: Box::new(X11ClipboardContext::::new().unwrap()), + }, + #[cfg(not(target_os = "linux"))] + _ => Clipboard { + clipboard: Box::new(ClipboardContext::new().unwrap()), }, - _ => Box::new(ClipboardContext::new().unwrap()), })) .ok(); } -pub fn get_contents() -> Result { - CLIPBOARD.get().unwrap().lock().get_contents() +pub fn get_contents(register: &str) -> Result { + match register { + #[cfg(target_os = "linux")] + "*" => CLIPBOARD.get().unwrap().lock().selection.get_contents(), + _ => CLIPBOARD.get().unwrap().lock().clipboard.get_contents(), + } } -pub fn set_contents(lines: String) -> Result<()> { - CLIPBOARD.get().unwrap().lock().set_contents(lines) +pub fn set_contents(lines: String, register: &str) -> Result<()> { + match register { + #[cfg(target_os = "linux")] + "*" => CLIPBOARD + .get() + .unwrap() + .lock() + .selection + .set_contents(lines), + _ => CLIPBOARD + .get() + .unwrap() + .lock() + .clipboard + .set_contents(lines), + } } diff --git a/src/cmd_line.rs b/src/cmd_line.rs index ee16a80717..f5544779cd 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -67,11 +67,11 @@ pub struct CmdLineSettings { #[arg(long = "title-hidden", env = "NEOVIDE_TITLE_HIDDEN", value_parser = FalseyValueParser::new())] pub title_hidden: bool, - /// Spawn a child process and leak it [DEFAULT] + /// Spawn a child process and leak it #[arg(long = "fork", env = "NEOVIDE_FORK", action = ArgAction::SetTrue, default_value = "0", value_parser = FalseyValueParser::new())] pub fork: bool, - /// Be "blocking" and let the shell persist as parent process. Takes precedence over `--fork`. + /// Be "blocking" and let the shell persist as parent process. Takes precedence over `--fork`. [DEFAULT] #[arg(long = "no-fork", action = ArgAction::SetTrue, value_parser = FalseyValueParser::new())] _no_fork: bool, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 884faa057c..9239fc24d3 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -34,13 +34,37 @@ pub use window::*; const MODE_CMDLINE: u64 = 4; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SortOrder { + pub z_index: u64, + composition_order: u64, +} + +impl Ord for SortOrder { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // The windows are sorted primarily by z_index, and inside the z_index by + // composition_order. The composition_order is the window creation order with the special + // case that every time a floating window is activated it gets the highest priority for + // its z_index. + let a = (self.z_index, (self.composition_order as i64)); + let b = (other.z_index, (other.composition_order as i64)); + a.cmp(&b) + } +} + +impl PartialOrd for SortOrder { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct AnchorInfo { pub anchor_grid_id: u64, pub anchor_type: WindowAnchor, pub anchor_left: f64, pub anchor_top: f64, - pub sort_order: u64, + pub sort_order: SortOrder, } impl WindowAnchor { @@ -69,6 +93,7 @@ pub struct Editor { pub current_mode_index: Option, pub ui_ready: bool, event_loop_proxy: EventLoopProxy, + composition_order: u64, } impl Editor { @@ -82,6 +107,7 @@ impl Editor { current_mode_index: None, ui_ready: false, event_loop_proxy, + composition_order: 0, } } @@ -245,23 +271,28 @@ impl Editor { anchor_grid, anchor_column: anchor_left, anchor_row: anchor_top, - sort_order, + z_index, .. } => { tracy_zone!("EditorWindowFloatPosition"); + self.composition_order += 1; self.set_window_float_position( grid, anchor_grid, anchor, anchor_left, anchor_top, - sort_order, + SortOrder { + z_index, + composition_order: self.composition_order, + }, ) } RedrawEvent::WindowHide { grid } => { tracy_zone!("EditorWindowHide"); - let window = self.windows.get(&grid); + let window = self.windows.get_mut(&grid); if let Some(window) = window { + window.anchor_info = None; window.hide(); } } @@ -335,7 +366,7 @@ impl Editor { let anchor_type = anchor_info.anchor_type.clone(); let anchor_left = anchor_info.anchor_left; let anchor_top = anchor_info.anchor_top; - let sort_order = Some(anchor_info.sort_order); + let sort_order = anchor_info.sort_order.clone(); self.set_window_float_position( grid, anchor_grid_id, @@ -389,7 +420,7 @@ impl Editor { anchor_type: WindowAnchor, anchor_left: f64, anchor_top: f64, - sort_order: Option, + sort_order: SortOrder, ) { if anchor_grid == grid { warn!("NeoVim requested a window to float relative to itself. This is not supported."); @@ -408,13 +439,25 @@ impl Editor { modified_top += parent_top; } + // Only update the sort order if it's the first position request (no anchor_info), or + // the z_index changes + let sort_order = if let Some(anchor_info) = &window.anchor_info { + if sort_order.z_index == anchor_info.sort_order.z_index { + anchor_info.sort_order.clone() + } else { + sort_order + } + } else { + sort_order + }; + window.position( Some(AnchorInfo { anchor_grid_id: anchor_grid, anchor_type, anchor_left, anchor_top, - sort_order: sort_order.unwrap_or(grid), + sort_order, }), (width, height), (modified_left, modified_top), @@ -426,6 +469,7 @@ impl Editor { } fn set_message_position(&mut self, grid: u64, grid_top: u64, scrolled: bool) { + let z_index = 250; // From the Neovim source code let parent_width = self .windows .get(&1) @@ -437,7 +481,10 @@ impl Editor { anchor_type: WindowAnchor::NorthWest, anchor_left: 0.0, anchor_top: grid_top as f64, - sort_order: u64::MAX, + sort_order: SortOrder { + z_index, + composition_order: self.composition_order, + }, }; if let Some(window) = self.windows.get_mut(&grid) { @@ -488,10 +535,23 @@ impl Editor { } fn set_cursor_position(&mut self, grid: u64, grid_left: u64, grid_top: u64) { + let mut window = self.windows.get_mut(&grid); + if let Some(window) = &mut window { + if let Some(anchor) = window.anchor_info.as_mut() { + // Neovim moves a window to the top of the layer each time the cursor enters it, so do the same here as well + self.composition_order += 1; + anchor.sort_order.composition_order = self.composition_order; + self.draw_command_batcher.queue(DrawCommand::Window { + grid_id: grid, + command: WindowDrawCommand::SortOrder(anchor.sort_order.clone()), + }); + } + } + if let Some(Window { window_type: WindowType::Message { .. }, .. - }) = self.windows.get(&grid) + }) = window { // When the user presses ":" to type a command, the cursor is sent to the gutter // in position 1 (right after the ":"). In all other cases, we want to skip diff --git a/src/error_handling.rs b/src/error_handling.rs index ecb01daef5..b0e861545f 100644 --- a/src/error_handling.rs +++ b/src/error_handling.rs @@ -9,6 +9,9 @@ use itertools::Itertools; use log::error; use winit::{error::EventLoopError, event_loop::EventLoop}; +#[cfg(target_os = "windows")] +use crate::windows_attach_to_console; + use crate::{ bridge::{send_ui, ParallelCommand}, running_tracker::RUNNING_TRACKER, @@ -57,36 +60,30 @@ fn format_and_log_error_message(err: Error) -> String { Neovide just crashed :( This is the error that caused the crash. In case you don't know what to do with this, please feel free to report this on https://github.com/neovide/neovide/issues! -{:?}", - err +{err:?}" ); log::error!("{}", msg); msg } fn handle_terminal_startup_errors(err: Error) -> i32 { - if let Some(clap_error) = err.downcast_ref::() { - let _ = clap_error.print(); - clap_error.exit_code() - } else { - eprintln!("{}", &format_and_log_error_message(err)); - 1 - } + eprintln!("{}", &format_and_log_error_message(err)); + 1 } fn handle_gui_startup_errors(err: Error, event_loop: EventLoop) -> i32 { - if let Some(clap_error) = err.downcast_ref::() { - let text = clap_error.render().to_string(); - show_error_window(&text, event_loop); - clap_error.exit_code() - } else { - show_error_window(&format_and_log_error_message(err), event_loop); - 1 - } + show_error_window(&format_and_log_error_message(err), event_loop); + 1 } pub fn handle_startup_errors(err: Error, event_loop: EventLoop) -> i32 { - if stdout().is_terminal() { + // Command line output is always printed to the stdout/stderr + if let Some(clap_error) = err.downcast_ref::() { + #[cfg(target_os = "windows")] + windows_attach_to_console(); + let _ = clap_error.print(); + clap_error.exit_code() + } else if stdout().is_terminal() { handle_terminal_startup_errors(err) } else { handle_gui_startup_errors(err, event_loop) diff --git a/src/main.rs b/src/main.rs index 6dcc7fe847..ffc02eb772 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,12 +90,16 @@ fn main() -> NeovideExitCode { windows_fix_dpi(); } + // This variable is set by the AppImage runtime and causes problems for child processes + #[cfg(target_os = "linux")] + env::remove_var("ARGV0"); + let event_loop = create_event_loop(); + clipboard::init(&event_loop); match setup(event_loop.create_proxy()) { Err(err) => handle_startup_errors(err, event_loop).into(), Ok((window_size, font_settings, _runtime)) => { - clipboard::init(&event_loop); main_loop(window_size, font_settings, event_loop).into() } } @@ -173,6 +177,11 @@ fn setup( // // The Window event loop sends UICommand to the bridge, which forwards them to Neovim. It also // reads `DrawCommand`, `SettingChanged`, and `WindowCommand` from the other components. + + SETTINGS.register::(); + SETTINGS.register::(); + SETTINGS.register::(); + let config = Config::init(); Config::watch_config_file(config.clone(), proxy.clone()); @@ -188,9 +197,6 @@ fn setup( trace!("Neovide version: {}", crate_version!()); - SETTINGS.register::(); - SETTINGS.register::(); - SETTINGS.register::(); let window_settings = load_last_window_settings().ok(); let window_size = determine_window_size(window_settings.as_ref()); let grid_size = match window_size { @@ -246,7 +252,6 @@ fn maybe_disown() { .stdin(process::Stdio::null()) .stdout(process::Stdio::null()) .stderr(process::Stdio::null()) - .arg("--no-fork") .args(env::args().skip(1)) .spawn() .is_ok()); diff --git a/src/renderer/cursor_renderer/mod.rs b/src/renderer/cursor_renderer/mod.rs index e2cda45fb6..30a89df21f 100644 --- a/src/renderer/cursor_renderer/mod.rs +++ b/src/renderer/cursor_renderer/mod.rs @@ -317,7 +317,7 @@ impl CursorRenderer { canvas.save(); canvas.clip_path(&path, None, Some(false)); - let y_adjustment = grid_renderer.shaper.y_adjustment(); + let baseline_offset = grid_renderer.shaper.baseline_offset(); let style = &self.cursor.grid_cell.1; let coarse_style = style.as_ref().map(|style| style.into()).unwrap_or_default(); @@ -326,7 +326,7 @@ impl CursorRenderer { for blob in blobs.iter() { canvas.draw_text_blob( blob, - (self.destination.x, self.destination.y + y_adjustment), + (self.destination.x, self.destination.y + baseline_offset), &paint, ); } diff --git a/src/renderer/d3d.rs b/src/renderer/d3d.rs index 1153f1164f..e08a7ac546 100644 --- a/src/renderer/d3d.rs +++ b/src/renderer/d3d.rs @@ -15,8 +15,6 @@ use windows::Win32::Graphics::Direct3D12::{ D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_DESC, D3D12_COMMAND_QUEUE_FLAG_NONE, D3D12_FENCE_FLAG_NONE, D3D12_RESOURCE_STATE_PRESENT, }; -#[cfg(feature = "d3d_debug")] -use windows::Win32::Graphics::Direct3D12::{D3D12GetDebugInterface, ID3D12Debug}; use windows::Win32::Graphics::DirectComposition::{ DCompositionCreateDevice2, IDCompositionDevice, IDCompositionTarget, IDCompositionVisual, }; @@ -25,10 +23,15 @@ use windows::Win32::Graphics::Dxgi::Common::{ DXGI_SAMPLE_DESC, }; use windows::Win32::Graphics::Dxgi::{ - CreateDXGIFactory2, IDXGIAdapter1, IDXGIFactory4, IDXGISwapChain1, IDXGISwapChain3, - DXGI_ADAPTER_FLAG, DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_CREATE_FACTORY_DEBUG, DXGI_SCALING_STRETCH, - DXGI_SWAP_CHAIN_DESC1, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, - DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, DXGI_USAGE_RENDER_TARGET_OUTPUT, + CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory2, IDXGISwapChain1, IDXGISwapChain3, + DXGI_ADAPTER_FLAG, DXGI_ADAPTER_FLAG_SOFTWARE, DXGI_SCALING_STRETCH, DXGI_SWAP_CHAIN_DESC1, + DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + DXGI_USAGE_RENDER_TARGET_OUTPUT, +}; +#[cfg(feature = "d3d_debug")] +use windows::Win32::Graphics::{ + Direct3D12::{D3D12GetDebugInterface, ID3D12Debug}, + Dxgi::{CreateDXGIFactory2, DXGI_CREATE_FACTORY_DEBUG}, }; use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObjectEx, INFINITE}; use winit::{ @@ -46,7 +49,7 @@ use crate::{ window::UserEvent, }; -fn get_hardware_adapter(factory: &IDXGIFactory4) -> Result { +fn get_hardware_adapter(factory: &IDXGIFactory2) -> Result { tracy_zone!("get_hardware_adapter"); for i in 0.. { let adapter = unsafe { factory.EnumAdapters1(i)? }; @@ -104,7 +107,7 @@ impl D3DSkiaRenderer { pub fn new(window: Window) -> Self { tracy_zone!("D3DSkiaRenderer::new"); #[cfg(feature = "d3d_debug")] - unsafe { + let dxgi_factory: IDXGIFactory2 = unsafe { let mut debug_controller: Option = None; D3D12GetDebugInterface(&mut debug_controller) .expect("Failed to create Direct3D debug controller"); @@ -112,12 +115,14 @@ impl D3DSkiaRenderer { debug_controller .expect("Failed to enable debug layer") .EnableDebugLayer(); - } - let dxgi_factory: IDXGIFactory4 = unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG).expect("Failed to create DXGI factory") }; + #[cfg(not(feature = "d3d_debug"))] + let dxgi_factory: IDXGIFactory2 = + unsafe { CreateDXGIFactory1().expect("Failed to create DXGI factory") }; + let adapter = get_hardware_adapter(&dxgi_factory) .expect("Failed to find any suitable Direct3D 12 adapters"); diff --git a/src/renderer/fonts/caching_shaper.rs b/src/renderer/fonts/caching_shaper.rs index 54e82d519b..9a49378c1b 100644 --- a/src/renderer/fonts/caching_shaper.rs +++ b/src/renderer/fonts/caching_shaper.rs @@ -212,16 +212,20 @@ impl CachingShaper { pub fn underline_position(&mut self) -> f32 { let metrics = self.metrics(); - metrics.ascent - metrics.underline_offset + self.baseline_offset() - metrics.underline_offset } pub fn stroke_size(&mut self) -> f32 { self.metrics().stroke_size } - pub fn y_adjustment(&mut self) -> f32 { + pub fn baseline_offset(&mut self) -> f32 { let metrics = self.metrics(); - metrics.ascent + metrics.leading + self.linespace / 2. + // NOTE: leading is also called linegap and should be equally distributed on the top and + // bottom, so it's centered like our linespace settings. That's how it works on the web, + // but some desktop applications only use the top according to: + // https://googlefonts.github.io/gf-guide/metrics.html#8-linegap-values-must-be-0 + metrics.ascent + (metrics.leading + self.linespace) / 2.0 } fn build_clusters( diff --git a/src/renderer/fonts/font_options.rs b/src/renderer/fonts/font_options.rs index 09b5797bb0..782d777923 100644 --- a/src/renderer/fonts/font_options.rs +++ b/src/renderer/fonts/font_options.rs @@ -204,7 +204,12 @@ impl FontOptions { .map(|fonts| { fonts .iter() - .flat_map(|font| font.fallback(&self.normal)) + .filter(|font| font.family.is_some()) + .map(|font| FontDescription { + family: font.family.clone().unwrap(), + style: None, + }) + .chain(self.normal.iter().cloned()) .collect() }) .unwrap_or_else(|| self.normal.clone()); @@ -212,7 +217,7 @@ impl FontOptions { fonts .into_iter() .map(|font| FontDescription { - style: font.style.or_else(|| style.name().map(str::to_string)), + style: style.name().map(str::to_string), ..font }) .collect() @@ -385,30 +390,11 @@ impl FontDescription { } } -impl SecondaryFontDescription { - pub fn fallback(&self, primary: &[FontDescription]) -> Vec { - if let Some(family) = &self.family { - vec![FontDescription { - family: family.clone(), - style: self.style.clone(), - }] - } else { - primary - .iter() - .map(|font| FontDescription { - family: font.family.clone(), - style: self.style.clone(), - }) - .collect() - } - } -} - impl fmt::Display for FontDescription { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.family)?; if let Some(style) = &self.style { - write!(f, " {}", style)?; + write!(f, " {style}")?; } Ok(()) } @@ -522,8 +508,7 @@ mod tests { assert_eq!( err, INVALID_SIZE_ERR, - "parse err should equal {}, but {}", - INVALID_SIZE_ERR, err, + "parse err should equal {INVALID_SIZE_ERR}, but {err}", ); } @@ -534,8 +519,7 @@ mod tests { assert_eq!( err, INVALID_WIDTH_ERR, - "parse err should equal {}, but {}", - INVALID_WIDTH_ERR, err, + "parse err should equal {INVALID_WIDTH_ERR}, but {err}", ); } diff --git a/src/renderer/grid_renderer.rs b/src/renderer/grid_renderer.rs index aa8568723e..6c16ef1e0e 100644 --- a/src/renderer/grid_renderer.rs +++ b/src/renderer/grid_renderer.rs @@ -197,7 +197,7 @@ impl GridRenderer { let trimmed = trimmed.trim_end(); let adjustment = PixelVec::new( leading_spaces as f32 * self.grid_scale.width(), - self.shaper.y_adjustment(), + self.shaper.baseline_offset(), ); if !trimmed.is_empty() { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 4d0c73834b..99f04b9c1e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -29,6 +29,7 @@ use winit::{ use crate::{ bridge::EditorMode, + cmd_line::CmdLineSettings, editor::{Cursor, Style}, profiling::{tracy_create_gpu_context, tracy_named_frame, tracy_zone}, renderer::rendered_layer::{group_windows, FloatingLayer}, @@ -50,9 +51,6 @@ use skia_safe::graphics::{ #[cfg(feature = "gpu_profiling")] use crate::profiling::GpuCtx; -#[cfg(target_os = "windows")] -use crate::CmdLineSettings; - use cursor_renderer::CursorRenderer; pub use fonts::caching_shaper::CachingShaper; pub use grid_renderer::GridRenderer; @@ -99,6 +97,7 @@ pub struct RendererSettings { underline_stroke_scale: f32, text_gamma: f32, text_contrast: f32, + experimental_layer_grouping: bool, } impl Default for RendererSettings { @@ -119,6 +118,7 @@ impl Default for RendererSettings { underline_stroke_scale: 1., text_gamma: 0.0, text_contrast: 0.5, + experimental_layer_grouping: false, } } } @@ -152,8 +152,8 @@ pub struct Renderer { pub window_regions: Vec, profiler: profiler::Profiler, - os_scale_factor: f64, - user_scale_factor: f64, + pub os_scale_factor: f64, + pub user_scale_factor: f64, } /// Results of processing the draw commands from the command channel. @@ -207,7 +207,10 @@ impl Renderer { let default_background = self.grid_renderer.get_default_background(); let grid_scale = self.grid_renderer.grid_scale; - let transparency = { SETTINGS.get::().transparency }; + let transparency = SETTINGS.get::().transparency; + let layer_grouping = SETTINGS + .get::() + .experimental_layer_grouping; root_canvas.clear(default_background.with_a((255.0 * transparency) as u8)); root_canvas.save(); root_canvas.reset_matrix(); @@ -238,14 +241,17 @@ impl Renderer { let mut current_windows = vec![]; for window in floating_windows { - let zindex = window.anchor_info.as_ref().unwrap().sort_order; + let zindex = window.anchor_info.as_ref().unwrap().sort_order.z_index; log::debug!("zindex: {}, base: {}", zindex, base_zindex); - // Group floating windows by consecutive z indices - if zindex - last_zindex > 1 && !current_windows.is_empty() { - for windows in group_windows(current_windows, grid_scale) { - floating_layers.push(FloatingLayer { windows }); + if !current_windows.is_empty() && zindex != last_zindex { + // Group floating windows by consecutive z indices if layer_grouping is enabled, + // Otherwise group all windows inside a single layer + if !layer_grouping || zindex - last_zindex > 1 { + for windows in group_windows(current_windows, grid_scale) { + floating_layers.push(FloatingLayer { windows }); + } + current_windows = vec![]; } - current_windows = vec![]; } if current_windows.is_empty() { @@ -268,7 +274,7 @@ impl Renderer { layer .windows .iter() - .map(|w| (w.id, w.anchor_info.as_ref().unwrap().sort_order)) + .map(|w| (w.id, w.anchor_info.as_ref().unwrap().sort_order.clone())) .collect_vec() ); } @@ -282,7 +288,6 @@ impl Renderer { .map(|window| { window.draw( root_canvas, - &settings, default_background.with_a((255.0 * transparency) as u8), grid_scale, ) @@ -381,14 +386,6 @@ impl Renderer { } self.flush(&settings); - let user_scale_factor = SETTINGS.get::().scale_factor.into(); - if user_scale_factor != self.user_scale_factor { - self.user_scale_factor = user_scale_factor; - self.grid_renderer - .handle_scale_factor_update(self.os_scale_factor * self.user_scale_factor); - result.font_changed = true; - } - result } @@ -433,10 +430,14 @@ impl Renderer { warn!("ViewportMargins recieved before window was initialized"); } _ => { - error!( - "WindowDrawCommand: {:?} sent for uninitialized grid {}", - command, grid_id - ); + let settings = SETTINGS.get::(); + // Ignore the errors when not using multigrid, since Neovim wrongly sends some of these + if !settings.no_multi_grid { + error!( + "WindowDrawCommand: {:?} sent for uninitialized grid {}", + command, grid_id + ); + } } }, } @@ -486,31 +487,9 @@ impl Renderer { /// Defines how floating windows are sorted. fn floating_sort(window_a: &&mut RenderedWindow, window_b: &&mut RenderedWindow) -> Ordering { - // First, compare floating order - let mut ord = window_a - .anchor_info - .as_ref() - .unwrap() - .sort_order - .partial_cmp(&window_b.anchor_info.as_ref().unwrap().sort_order) - .unwrap(); - if ord == Ordering::Equal { - // if equal, compare grid pos x - ord = window_a - .grid_current_position - .x - .partial_cmp(&window_b.grid_current_position.x) - .unwrap(); - if ord == Ordering::Equal { - // if equal, compare grid pos z - ord = window_a - .grid_current_position - .y - .partial_cmp(&window_b.grid_current_position.y) - .unwrap(); - } - } - ord + let orda = &window_a.anchor_info.as_ref().unwrap().sort_order; + let ordb = &window_b.anchor_info.as_ref().unwrap().sort_order; + orda.cmp(ordb) } pub enum WindowConfigType { diff --git a/src/renderer/opengl.rs b/src/renderer/opengl.rs index 9088d25b9b..4ca4e3e15e 100644 --- a/src/renderer/opengl.rs +++ b/src/renderer/opengl.rs @@ -121,7 +121,7 @@ impl OpenGLSkiaRenderer { }) .expect("Could not create interface"); - let mut gr_context = skia_safe::gpu::DirectContext::new_gl(interface, None) + let mut gr_context = skia_safe::gpu::direct_contexts::make_gl(interface, None) .expect("Could not create direct context"); let fb_info = { let mut fboid: GLint = 0; diff --git a/src/renderer/rendered_layer.rs b/src/renderer/rendered_layer.rs index ac279a973d..0b485c3a2c 100644 --- a/src/renderer/rendered_layer.rs +++ b/src/renderer/rendered_layer.rs @@ -3,9 +3,11 @@ use skia_safe::{ canvas::SaveLayerRec, image_filters::blur, utils::shadow_utils::{draw_shadow, ShadowFlags}, - BlendMode, Canvas, ClipOp, Color, Contains, Paint, Path, PathOp, Point3, Rect, + BlendMode, Canvas, ClipOp, Color, Paint, Path, PathOp, Point3, Rect, }; +use glamour::Intersection; + use crate::units::{to_skia_rect, GridScale, PixelRect}; use super::{RenderedWindow, RendererSettings, WindowDrawDetails}; @@ -82,22 +84,12 @@ impl<'w> FloatingLayer<'w> { .map(|window| window.pixel_region(grid_scale)) .collect::>(); - let blend = self.uniform_background_blend(); - - self.windows.iter_mut().for_each(|window| { - window.update_blend(blend); - }); - let mut ret = vec![]; (0..self.windows.len()).for_each(|i| { let window = &mut self.windows[i]; window.draw_background_surface(root_canvas, regions[i], grid_scale); - }); - (0..self.windows.len()).for_each(|i| { - let window = &mut self.windows[i]; window.draw_foreground_surface(root_canvas, regions[i], grid_scale); - ret.push(WindowDrawDetails { id: window.id, region: regions[i], @@ -111,14 +103,6 @@ impl<'w> FloatingLayer<'w> { ret } - pub fn uniform_background_blend(&self) -> u8 { - self.windows - .iter() - .filter_map(|window| window.get_smallest_blend_value()) - .min() - .unwrap_or(0) - } - fn _draw_shadow(&self, root_canvas: &Canvas, path: &Path, settings: &RendererSettings) { if !settings.floating_shadow { return; @@ -160,18 +144,15 @@ fn get_window_group(windows: &mut Vec, index: usize) -> usize { windows[index].group } -fn rect_contains(a: &Rect, b: &Rect) -> bool { - Rect::contains(a, b) || Rect::contains(b, a) -} - fn group_windows_with_regions(windows: &mut Vec, regions: &[PixelRect]) { + // intersects does not consider touching regions as intersection, so extend the box by one + // pixel before doing the test. + let epsilon = 1.0; for i in 0..windows.len() { for j in i + 1..windows.len() { let group_i = get_window_group(windows, i); let group_j = get_window_group(windows, j); - if group_i != group_j - && rect_contains(&to_skia_rect(®ions[i]), &to_skia_rect(®ions[j])) - { + if group_i != group_j && regions[i].to_rect().inflate((epsilon, epsilon).into()).intersects(®ions[j]) { let new_group = group_i.min(group_j); if group_i != group_j { windows[group_i].group = new_group; @@ -202,9 +183,10 @@ pub fn group_windows( for i in 0..windows.len() { let _ = get_window_group(&mut windows, i); } + windows.sort_by(|a, b| a.group.cmp(&b.group)); windows .into_iter() - .group_by(|window| window.group) + .chunk_by(|window| window.group) .into_iter() .map(|(_, v)| v.map(|w| w.window).collect::>()) .collect_vec() diff --git a/src/renderer/rendered_window.rs b/src/renderer/rendered_window.rs index 92b3c091fd..83755f8115 100644 --- a/src/renderer/rendered_window.rs +++ b/src/renderer/rendered_window.rs @@ -1,14 +1,12 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use skia_safe::{ - canvas::SaveLayerRec, - utils::shadow_utils::{draw_shadow, ShadowFlags}, - BlendMode, Canvas, ClipOp, Color, Matrix, Paint, Path, Picture, PictureRecorder, Point3, Rect, + canvas::SaveLayerRec, BlendMode, Canvas, Color, Matrix, Paint, Picture, PictureRecorder, Rect, }; use crate::{ cmd_line::CmdLineSettings, - editor::{AnchorInfo, Style, WindowType}, + editor::{AnchorInfo, SortOrder, Style, WindowType}, profiling::{tracy_plot, tracy_zone}, renderer::{animation_utils::*, GridRenderer, RendererSettings}, settings::SETTINGS, @@ -63,6 +61,7 @@ pub enum WindowDrawCommand { left: u64, right: u64, }, + SortOrder(SortOrder), } #[derive(Clone)] @@ -70,7 +69,7 @@ struct Line { line_fragments: Vec, background_picture: Option, foreground_picture: Option, - blend: u8, + has_transparency: bool, is_valid: bool, } @@ -93,8 +92,6 @@ pub struct RenderedWindow { position_t: f32, pub scroll_animation: CriticallyDampedSpringAnimation, - - has_transparency: bool, } #[derive(Clone, Debug)] @@ -113,19 +110,6 @@ impl WindowDrawDetails { } } -impl Line { - fn update_background_blend(&mut self, blend: u8) { - if self.blend != blend { - self.blend = blend; - self.is_valid = false; - } - } - - fn has_transparency(&self) -> bool { - self.blend > 0 - } -} - impl RenderedWindow { pub fn new(id: u64, grid_position: GridPos, grid_size: GridSize) -> RenderedWindow { RenderedWindow { @@ -147,8 +131,6 @@ impl RenderedWindow { position_t: 2.0, // 2.0 is out of the 0.0 to 1.0 range and stops animation. scroll_animation: CriticallyDampedSpringAnimation::new(), - - has_transparency: false, } } @@ -159,13 +141,6 @@ impl RenderedWindow { ) * grid_scale } - pub fn update_blend(&self, blend: u8) { - for (_, line) in self.iter_lines() { - let mut line = line.borrow_mut(); - line.update_background_blend(blend); - } - } - fn get_target_position(&self, grid_rect: &GridRect) -> GridPos { let destination = self.grid_destination + grid_rect.min.to_vector(); @@ -241,8 +216,6 @@ impl RenderedWindow { pixel_region: PixelRect, grid_scale: GridScale, ) { - let mut has_transparency = false; - let inner_region = self.inner_region(pixel_region, grid_scale); canvas.save(); @@ -250,7 +223,6 @@ impl RenderedWindow { for (matrix, line) in self.iter_border_lines_with_transform(pixel_region, grid_scale) { let line = line.borrow(); if let Some(background_picture) = &line.background_picture { - has_transparency |= line.has_transparency(); canvas.draw_picture(background_picture, Some(&matrix), None); } } @@ -260,7 +232,6 @@ impl RenderedWindow { for (matrix, line) in self.iter_scrollable_lines_with_transform(pixel_region, grid_scale) { let line = line.borrow(); if let Some(background_picture) = &line.background_picture { - has_transparency |= line.has_transparency(); canvas.draw_picture(background_picture, Some(&matrix), None); pics += 1; } @@ -273,8 +244,6 @@ impl RenderedWindow { ); canvas.restore(); canvas.restore(); - - self.has_transparency = has_transparency; } pub fn draw_foreground_surface( @@ -310,49 +279,18 @@ impl RenderedWindow { scroll_offset_lines..scroll_offset_lines + self.grid_size.height as isize + 1, ) .flatten() - .any(|line| line.borrow().has_transparency()) + .any(|line| line.borrow().has_transparency) } pub fn draw( &mut self, root_canvas: &Canvas, - settings: &RendererSettings, default_background: Color, grid_scale: GridScale, ) -> WindowDrawDetails { let pixel_region_box = self.pixel_region(grid_scale); let pixel_region = to_skia_rect(&pixel_region_box); - if self.anchor_info.is_some() && settings.floating_shadow { - root_canvas.save(); - let shadow_path = Path::rect(pixel_region, None); - // We clip using the Difference op to make sure that the shadow isn't rendered inside - // the window itself. - root_canvas.clip_path(&shadow_path, Some(ClipOp::Difference), None); - // The light angle is specified in degrees from the vertical, so we first convert them - // to radians and then use sin/cos to get the y and z components of the light - let light_angle_radians = settings.light_angle_degrees.to_radians(); - draw_shadow( - root_canvas, - &shadow_path, - // Specifies how far from the root canvas the shadow casting rect is. We just use - // the z component here to set it a constant distance away. - Point3::new(0., 0., settings.floating_z_height), - // Because we use the DIRECTIONAL_LIGHT shadow flag, this specifies the angle that - // the light is coming from. - Point3::new(0., -light_angle_radians.sin(), light_angle_radians.cos()), - // This is roughly equal to the apparent radius of the light . - 5., - Color::from_argb((0.03 * 255.) as u8, 0, 0, 0), - Color::from_argb((0.35 * 255.) as u8, 0, 0, 0), - // Directional Light flag is necessary to make the shadow render consistently - // across various sizes of floating windows. It effects how the light direction is - // processed. - Some(ShadowFlags::DIRECTIONAL_LIGHT), - ); - root_canvas.restore(); - } - root_canvas.save(); root_canvas.clip_rect(pixel_region, None, Some(false)); @@ -454,7 +392,7 @@ impl RenderedWindow { line_fragments, background_picture: None, foreground_picture: None, - blend: 0, + has_transparency: false, is_valid: false, }; @@ -507,6 +445,11 @@ impl RenderedWindow { WindowDrawCommand::ViewportMargins { top, bottom, .. } => { self.viewport_margins = ViewportMargins { top, bottom } } + WindowDrawCommand::SortOrder(sort_order) => { + if let Some(anchor_info) = self.anchor_info.as_mut() { + anchor_info.sort_order = sort_order; + } + } _ => {} }; } @@ -597,10 +540,6 @@ impl RenderedWindow { }) } - fn iter_lines(&self) -> impl Iterator>)> { - self.iter_border_lines().chain(self.iter_scrollable_lines()) - } - fn iter_scrollable_lines_with_transform( &self, pixel_region: PixelRect, @@ -651,28 +590,7 @@ impl RenderedWindow { to_skia_rect(&adjusted_region) } - pub fn get_smallest_blend_value(&self) -> Option { - let height = self.grid_size.height as isize; - if height == 0 { - return None; - } - let mut smallest_blend_value: Option = None; - - for (_, line) in self.iter_lines() { - let line = line.borrow(); - line.line_fragments.iter().for_each(|f| { - if let Some(style) = &f.style { - smallest_blend_value = - Some(smallest_blend_value.map_or(style.blend, |v| v.min(style.blend))); - } - }); - } - - smallest_blend_value - } - pub fn prepare_lines(&mut self, grid_renderer: &mut GridRenderer, force: bool) { - log::trace!("prepare_lines force: {force}"); let scroll_offset_lines = self.scroll_animation.position.floor() as isize; let height = self.grid_size.height as isize; if height == 0 { @@ -692,7 +610,7 @@ impl RenderedWindow { let grid_rect = Rect::from_wh(line_size.width, line_size.height); let canvas = recorder.begin_recording(grid_rect, None); - let mut blend = 0; + let mut has_transparency = false; let mut custom_background = false; for line_fragment in line.line_fragments.iter() { @@ -710,7 +628,7 @@ impl RenderedWindow { style, ); custom_background |= background_info.custom_color; - blend = blend.min(style.as_ref().map_or(0, |s| s.blend)); + has_transparency |= background_info.transparent; } let background_picture = custom_background.then_some(recorder.finish_recording_as_picture(None).unwrap()); @@ -739,7 +657,7 @@ impl RenderedWindow { line.background_picture = background_picture; line.foreground_picture = foreground_picture; - line.blend = blend; + line.has_transparency = has_transparency; line.is_valid = true; }; diff --git a/src/settings/config.rs b/src/settings/config.rs index 873a10856b..331f5e59a8 100644 --- a/src/settings/config.rs +++ b/src/settings/config.rs @@ -39,19 +39,19 @@ pub fn config_path() -> PathBuf { #[derive(Debug, Deserialize, Default, Clone)] #[serde(rename_all = "kebab-case")] pub struct Config { - pub wsl: Option, - pub no_multigrid: Option, - pub maximized: Option, - pub vsync: Option, - pub srgb: Option, + pub font: Option, pub fork: Option, + pub frame: Option, pub idle: Option, + pub maximized: Option, pub neovim_bin: Option, - pub frame: Option, + pub no_multigrid: Option, + pub srgb: Option, + pub tabs: Option, pub theme: Option, - pub font: Option, pub title_hidden: Option, - pub tabs: Option, + pub vsync: Option, + pub wsl: Option, } #[derive(Debug, Clone, PartialEq)] @@ -160,7 +160,7 @@ fn watcher_thread(init_config: Config, event_loop_proxy: EventLoopProxy for FontOptions { #[cfg(test)] mod tests { + use crate::renderer::fonts::font_options::CoarseStyle; + use super::*; #[test] @@ -220,4 +222,82 @@ mod tests { _ => panic!("Unexpected value"), } } + + #[test] + fn test_secondary_font_not_found_fallback() { + let settings = r#" + { + "normal": ["Consolas", "Noto Emoji"], + "bold": "NotFound", + "size": 19 + } + "#; + + let settings: FontSettings = serde_json::from_str(settings).unwrap(); + let options = FontOptions::from(settings); + let style: CoarseStyle = CoarseStyle::permutations() + .filter(|style| style.name() == Some("Bold")) + .collect::>()[0]; + let fonts = options.font_list(style); + + assert_eq!(fonts.len(), 3); + assert_eq!( + fonts, + vec![ + FontDescription { + family: "NotFound".into(), + style: Some("Bold".into()) + }, + FontDescription { + family: "Consolas".into(), + style: Some("Bold".into()) + }, + FontDescription { + family: "Noto Emoji".into(), + style: Some("Bold".into()) + } + ] + ); + } + + #[test] + fn test_oneof_secondary_font_not_found_fallback() { + let settings = r#" + { + "normal": ["Consolas", "Noto Emoji"], + "bold": ["NotFound", "Menlo"], + "size": 19 + } + "#; + + let settings: FontSettings = serde_json::from_str(settings).unwrap(); + let options = FontOptions::from(settings); + let style: CoarseStyle = CoarseStyle::permutations() + .filter(|style| style.name() == Some("Bold")) + .collect::>()[0]; + let fonts = options.font_list(style); + + assert_eq!(fonts.len(), 4); + assert_eq!( + fonts, + vec![ + FontDescription { + family: "NotFound".into(), + style: Some("Bold".into()) + }, + FontDescription { + family: "Menlo".into(), + style: Some("Bold".into()) + }, + FontDescription { + family: "Consolas".into(), + style: Some("Bold".into()) + }, + FontDescription { + family: "Noto Emoji".into(), + style: Some("Bold".into()) + } + ] + ); + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 886fefadd9..87f0bb4cc5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,8 +3,6 @@ mod ring_buffer; mod test; pub use ring_buffer::*; -#[cfg(test)] -pub use test::*; #[cfg(not(target_os = "windows"))] pub fn is_tty() -> bool { diff --git a/src/utils/test.rs b/src/utils/test.rs index 15dd96efa9..c1df25a0dd 100644 --- a/src/utils/test.rs +++ b/src/utils/test.rs @@ -69,51 +69,6 @@ pub fn ascii_to_points(ascii: &str) -> Vec { .collect() } -pub fn assert_points_eq(actual: Vec, expected_ascii: &str) { - let expected = ascii_to_points(expected_ascii); - if expected.len() != actual.len() || expected.iter().zip(actual.iter()).any(|(a, b)| a != b) { - let actual_ascii = points_to_ascii(actual); - panic!( - indoc! {" - Points do not match - Expected: - {} - Actual: - {} - "}, - expected_ascii, actual_ascii - ); - } -} - -pub fn points_to_ascii(points: Vec) -> String { - if points - .iter() - .any(|point| point.x.fract() != 0.0 || point.y.fract() != 0.) - { - panic!("Points must be integers to render as ascii"); - } - - let line_width = points.iter().map(|p| p.x as usize).max().unwrap() + 1; - let line_count = points.iter().map(|p| p.y as usize).max().unwrap() + 1; - let mut ascii = vec![vec![' '; line_width as usize]; line_count as usize]; - let numbers_big_enough = points.len() <= 9; - let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - for (index, p) in points.iter().enumerate() { - let char = if numbers_big_enough { - std::char::from_digit(index as u32, 10).unwrap() - } else { - chars.chars().nth(index).expect("Too many points") - }; - ascii[p.y as usize][p.x as usize] = char; - } - ascii - .iter() - .map(|line| line.iter().collect::()) - .collect::>() - .join("\n") -} - #[test] fn ascii_to_rect_works() { // Single rect diff --git a/src/window/error_window.rs b/src/window/error_window.rs index b9f1a81971..eb067656c0 100644 --- a/src/window/error_window.rs +++ b/src/window/error_window.rs @@ -248,7 +248,7 @@ impl State { true } "y" => { - let _ = clipboard::set_contents(message.to_string()); + let _ = clipboard::set_contents(message.to_string(), "+"); true } _ => false, diff --git a/src/window/keyboard_manager.rs b/src/window/keyboard_manager.rs index 26e642a7c7..bef6b8bcd8 100644 --- a/src/window/keyboard_manager.rs +++ b/src/window/keyboard_manager.rs @@ -301,7 +301,7 @@ fn get_special_key(key_event: &KeyEvent) -> Option<&str> { NamedKey::Space => { // Space can finish a dead key sequence, so treat space as a special key only when // that doesn't happen. - if key_event.text == Some(" ".into()) { + if key_event.text == Some(" ".into()) || key_event.text.is_none() { Some("Space") } else { None diff --git a/src/window/mouse_manager.rs b/src/window/mouse_manager.rs index 6be90f903e..3ba9d81826 100644 --- a/src/window/mouse_manager.rs +++ b/src/window/mouse_manager.rs @@ -65,7 +65,7 @@ struct TouchTrace { pub struct MouseManager { drag_details: Option, - grid_position: GridPos, + grid_position: GridPos, has_moved: bool, window_position: PixelPos, @@ -112,7 +112,7 @@ impl MouseManager { &self, window_details: &WindowDrawDetails, editor_state: &EditorState, - ) -> GridPos { + ) -> GridPos { let global_bounds = window_details.region; let clamped_position = clamp_position( self.window_position, @@ -123,8 +123,8 @@ impl MouseManager { (relative_position / *editor_state.grid_scale) .floor() - .try_cast() - .unwrap() + .max((0.0, 0.0).into()) + .try_cast().unwrap() } fn handle_pointer_motion(&mut self, position: PixelPos, editor_state: &EditorState) { @@ -160,7 +160,7 @@ impl MouseManager { send_ui(SerialCommand::Drag { button: mouse_button_to_button_text(drag_details.button).unwrap(), grid_id: window_details.event_grid_id(), - position: self.grid_position.try_cast::().unwrap().to_tuple(), + position: self.grid_position.to_tuple(), modifier_string: editor_state .keyboard_manager .format_modifier_string("", true), @@ -171,7 +171,7 @@ impl MouseManager { button: "move".into(), action: "".into(), // this is ignored by nvim grid_id: window_details.event_grid_id(), - position: relative_position.try_cast::().unwrap().to_tuple(), + position: relative_position.to_tuple(), modifier_string: editor_state .keyboard_manager .format_modifier_string("", true), @@ -211,7 +211,7 @@ impl MouseManager { button: button_text.clone(), action, grid_id: details.event_grid_id(), - position: position.try_cast::().unwrap().to_tuple(), + position: position.to_tuple(), modifier_string: editor_state .keyboard_manager .format_modifier_string("", true), @@ -258,7 +258,7 @@ impl MouseManager { let scroll_command = SerialCommand::Scroll { direction: input_type.to_string(), grid_id, - position: self.grid_position.try_cast::().unwrap().to_tuple(), + position: self.grid_position.to_tuple(), modifier_string: editor_state .keyboard_manager .format_modifier_string("", true), @@ -278,7 +278,7 @@ impl MouseManager { let scroll_command = SerialCommand::Scroll { direction: input_type.to_string(), grid_id, - position: self.grid_position.try_cast::().unwrap().to_tuple(), + position: self.grid_position.to_tuple(), modifier_string: editor_state .keyboard_manager .format_modifier_string("", true), diff --git a/src/window/settings.rs b/src/window/settings.rs index 430310f343..4ac17908a2 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,13 +1,12 @@ #[cfg(target_os = "macos")] use {log::error, rmpv::Value}; -use crate::{cmd_line::CmdLineSettings, settings::*}; +use crate::settings::*; #[derive(Clone, SettingGroup, PartialEq)] pub struct WindowSettings { pub refresh_rate: u64, pub refresh_rate_idle: u64, - pub idle: bool, pub transparency: f32, pub window_blurred: bool, pub scale_factor: f32, @@ -50,7 +49,6 @@ impl Default for WindowSettings { iso_layout: false, refresh_rate: 60, refresh_rate_idle: 5, - idle: SETTINGS.get::().idle, remember_window_size: true, remember_window_position: true, hide_mouse_when_typing: false, diff --git a/src/window/window_wrapper.rs b/src/window/window_wrapper.rs index 640e0fc362..d7d1cabb18 100644 --- a/src/window/window_wrapper.rs +++ b/src/window/window_wrapper.rs @@ -20,7 +20,7 @@ use crate::{ clamped_grid_size, FontSettings, HotReloadConfigs, SettingsChanged, DEFAULT_GRID_SIZE, MIN_GRID_SIZE, SETTINGS, }, - units::{GridPos, GridRect, GridSize, PixelPos, PixelSize}, + units::{GridRect, GridSize, PixelPos, PixelSize}, window::{create_window, PhysicalSize, ShouldRender, WindowSize}, CmdLineSettings, }; @@ -35,7 +35,7 @@ use log::trace; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; use winit::{ dpi, - event::{Event, WindowEvent}, + event::{Event, Ime, WindowEvent}, event_loop::{EventLoopProxy, EventLoopWindowTarget}, window::{Fullscreen, Theme}, }; @@ -68,17 +68,17 @@ pub struct WinitWindowWrapper { keyboard_manager: KeyboardManager, mouse_manager: MouseManager, title: String, - fullscreen: bool, font_changed_last_frame: bool, saved_inner_size: dpi::PhysicalSize, saved_grid_size: Option>, - ime_position: dpi::PhysicalPosition, requested_columns: Option, requested_lines: Option, ui_state: UIState, window_padding: WindowPadding, initial_window_size: WindowSize, is_minimized: bool, + ime_enabled: bool, + ime_area: (dpi::PhysicalPosition, dpi::PhysicalSize), pub vsync: Option, #[cfg(target_os = "macos")] pub macos_feature: Option, @@ -98,11 +98,9 @@ impl WinitWindowWrapper { keyboard_manager: KeyboardManager::new(), mouse_manager: MouseManager::new(), title: String::from("Neovide"), - fullscreen: false, font_changed_last_frame: false, saved_inner_size, saved_grid_size: None, - ime_position: dpi::PhysicalPosition::new(-1, -1), requested_columns: None, requested_lines: None, ui_state: UIState::Initing, @@ -115,23 +113,23 @@ impl WinitWindowWrapper { initial_window_size, is_minimized: false, vsync: None, + ime_enabled: false, + ime_area: Default::default(), #[cfg(target_os = "macos")] macos_feature: None, } } - pub fn toggle_fullscreen(&mut self) { + pub fn set_fullscreen(&mut self, fullscreen: bool) { if let Some(skia_renderer) = &self.skia_renderer { let window = skia_renderer.window(); - if self.fullscreen { - window.set_fullscreen(None); - } else { + if fullscreen { let handle = window.current_monitor(); window.set_fullscreen(Some(Fullscreen::Borderless(handle))); + } else { + window.set_fullscreen(None); } } - - self.fullscreen = !self.fullscreen; } #[cfg(target_os = "macos")] @@ -204,13 +202,19 @@ impl WinitWindowWrapper { self.requested_lines = lines.map(|v| v.try_into().unwrap()); } WindowSettingsChanged::Fullscreen(fullscreen) => { - if self.fullscreen != fullscreen { - self.toggle_fullscreen(); - } + self.set_fullscreen(fullscreen); } WindowSettingsChanged::InputIme(ime_enabled) => { self.set_ime(ime_enabled); } + WindowSettingsChanged::ScaleFactor(user_scale_factor) => { + let renderer = &mut self.renderer; + renderer.user_scale_factor = user_scale_factor.into(); + renderer.grid_renderer.handle_scale_factor_update( + renderer.os_scale_factor * renderer.user_scale_factor, + ); + self.font_changed_last_frame = true; + } WindowSettingsChanged::WindowBlurred(blur) => { if let Some(skia_renderer) = &self.skia_renderer { let WindowSettings { transparency, .. } = SETTINGS.get::(); @@ -397,6 +401,15 @@ impl WinitWindowWrapper { tracy_zone!("Moved"); vsync.update(skia_renderer.window()); } + WindowEvent::Ime(Ime::Enabled) => { + log::info!("Ime enabled"); + self.ime_enabled = true; + self.update_ime_position(true); + } + WindowEvent::Ime(Ime::Disabled) => { + log::info!("Ime disabled"); + self.ime_enabled = false; + } _ => { tracy_zone!("Unknown WindowEvent"); return false; @@ -459,6 +472,19 @@ impl WinitWindowWrapper { let window_config = create_window(event_loop, maximized, &self.title); let window = &window_config.window; + let WindowSettings { + input_ime, + theme, + transparency, + window_blurred, + fullscreen, + #[cfg(target_os = "macos")] + input_macos_option_key_is_meta, + .. + } = SETTINGS.get::(); + + window.set_ime_allowed(input_ime); + // It's important that this is created before the window is resized, since it can change the padding and affect the size #[cfg(target_os = "macos")] { @@ -469,9 +495,7 @@ impl WinitWindowWrapper { } let scale_factor = window.scale_factor(); - self.renderer - .grid_renderer - .handle_scale_factor_update(scale_factor); + self.renderer.handle_os_scale_factor_change(scale_factor); let mut size = PhysicalSize::default(); match self.initial_window_size { @@ -539,15 +563,11 @@ impl WinitWindowWrapper { self.renderer.grid_renderer.grid_scale ); - let WindowSettings { - input_ime, - theme, - transparency, - window_blurred, - .. - } = SETTINGS.get::(); - window.set_blur(window_blurred && transparency < 1.0); + if fullscreen { + let handle = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(handle))); + } match theme.as_str() { "light" => set_background("light"), @@ -571,11 +591,10 @@ impl WinitWindowWrapper { window.request_redraw(); } - // Ensure that the window has the correct IME state - self.set_ime(input_ime); - self.ui_state = UIState::FirstFrame; self.skia_renderer = Some(skia_renderer); + #[cfg(target_os = "macos")] + self.set_macos_option_as_meta(input_macos_option_key_is_meta); } fn handle_draw_commands(&mut self, batch: Vec) { @@ -655,7 +674,7 @@ impl WinitWindowWrapper { } } - self.update_ime_position(); + self.update_ime_position(false); should_render.update(self.renderer.prepare_frame()); @@ -755,26 +774,29 @@ impl WinitWindowWrapper { }); } - fn update_ime_position(&mut self) { - if self.skia_renderer.is_none() { + fn update_ime_position(&mut self, force: bool) { + if !self.ime_enabled || self.skia_renderer.is_none() { return; } let skia_renderer = self.skia_renderer.as_ref().unwrap(); let grid_scale = self.renderer.grid_renderer.grid_scale; let font_dimensions = GridSize::new(1.0, 1.0) * grid_scale; - let mut position = self.renderer.get_cursor_destination(); - position.y += font_dimensions.height; - let position: GridPos = (position / grid_scale).floor().try_cast().unwrap(); + let position = self.renderer.get_cursor_destination(); + let position = position.try_cast::().unwrap(); let position = dpi::PhysicalPosition { x: position.x, y: position.y, }; - if position != self.ime_position { - self.ime_position = position; - skia_renderer.window().set_ime_cursor_area( - dpi::Position::Physical(position), - dpi::PhysicalSize::new(100, font_dimensions.height as u32), - ); + // NOTE: some compositors don't like excluding too much and try to render popup at the + // bottom right corner of the provided area, so exclude just the full-width char to not + // obscure the cursor and not render popup at the end of the window. + let width = (font_dimensions.width * 2.0).ceil() as u32; + let height = font_dimensions.height.ceil() as u32; + let size = dpi::PhysicalSize::new(width, height); + let area = (position, size); + if force || self.ime_area != area { + self.ime_area = (position, size); + skia_renderer.window().set_ime_cursor_area(position, size); } } diff --git a/src/windows_utils.rs b/src/windows_utils.rs index 366be9ccdc..4a98129498 100644 --- a/src/windows_utils.rs +++ b/src/windows_utils.rs @@ -1,5 +1,6 @@ -use windows::Win32::UI::HiDpi::{ - SetProcessDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, +use windows::Win32::{ + System::Console::{AttachConsole, ATTACH_PARENT_PROCESS}, + UI::HiDpi::{SetProcessDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2}, }; use windows_registry::{Result, CURRENT_USER}; @@ -78,3 +79,10 @@ pub fn windows_fix_dpi() { .expect("Failed to set DPI awareness!"); } } + +pub fn windows_attach_to_console() { + // Attach to parent console tip found here: https://github.com/rust-lang/rust/issues/67159#issuecomment-987882771 + unsafe { + AttachConsole(ATTACH_PARENT_PROCESS).ok(); + } +} diff --git a/website/docs/config-file.md b/website/docs/config-file.md index 95ad30614a..a1aadc03d3 100644 --- a/website/docs/config-file.md +++ b/website/docs/config-file.md @@ -25,16 +25,22 @@ There are two types of settings: Settings currently available in the config file with default values: ```toml -wsl = false -no-multigrid = false -vsync = true -maximized = false -srgb = false +fork = false +frame = "full" idle = true +maximized = false neovim-bin = "/usr/bin/nvim" # in reality found dynamically on $PATH if unset -frame = "full" -title-hidden = true +no-multigrid = false +srgb = false tabs = true +theme = "auto" +title-hidden = true +vsync = true +wsl = false + +[font] +normal = [] # Will use the bundled Fira Code Nerd Font by default +size = 14.0 ``` Settings from environment variables can be found in [Command Line Reference](command-line-reference.md), @@ -90,5 +96,34 @@ normal = ["MonoLisa Nerd Font"] size = 18 [font.features] -MonoLisa = [ "+ss01", "+ss07", "+ss11", "-calt", "+ss09", "+ss02", "+ss14", "+ss16", "+ss17" ] +"MonoLisa Nerd Font" = [ "+ss01", "+ss07", "+ss11", "-calt", "+ss09", "+ss02", "+ss14" ] +``` + +Specify font weight: + +```toml +[font] +size = 19 +hinting = "full" +edging = "antialias" + +[[font.normal]] +family = "JetBrainsMono Nerd Font Propo" +style = "W400" + +# You can set a different font for fallback +[[font.normal]] +family = "Noto Sans CJK SC" +style = "Normal" + +[[font.bold]] +family = "JetBrainsMono Nerd Font Propo" +style = "W600" + +# No need to specify fallback in every variant, if omitted or specified here +# but not found, it will fallback to normal font with this weight which is bold +# in this case. +[[font.bold]] +family = "Noto Sans CJK SC" +style = "Bold" ``` diff --git a/website/docs/configuration.md b/website/docs/configuration.md index 96b15ba2f3..84d0804887 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -28,6 +28,26 @@ if vim.g.neovide then end ``` +You can also query the version with: + +```vim +echo g:neovide_version +``` + +Lua: + +```lua +vim.print(vim.g.neovide_version) +``` + +Or inspect the more detailed channel information: + +Lua: + +```lua +lua vim.print(vim.api.nvim_get_chan_info(vim.g.neovide_channel_id)) +``` + ### Display #### Font @@ -318,6 +338,23 @@ Draw a grey border around opaque windows only. Default: `false` +#### Position Animation Length + +VimScript: + +```vim +let g:neovide_position_animation_length = 0.15 +``` + +Lua: + +```lua +vim.g.neovide_position_animation_length = 0.15 +``` + +Determines the time it takes for a window to complete animation from one position to another +position in seconds, such as `:split`. Set to `0` to disable. + #### Scroll Animation Length VimScript: @@ -417,6 +454,26 @@ Set the [`background`](https://neovim.io/doc/user/options.html#'background') opt starts. Possible values: _light_, _dark_, _auto_. On systems that support it, _auto_ will mirror the system theme, and will update `background` when the system theme changes. +#### Layer grouping + +VimScript: + +```vim +let g:experimental_layer_grouping = v:false +``` + +Lua: + +```lua +vim.g.experimental_layer_grouping = false +``` + +**Available since 0.13.1.** + +Group non-emtpy consecutive layers (zindex) together, so that the shadows and blurring is done for +the whole group instead of each individual layer. This can get rid of some shadowing and blending +artifacts, but cause worse problems like [#2574](https://github.com/neovide/neovide/issues/2574). + ### Functionality #### Refresh Rate @@ -494,6 +551,26 @@ vim.g.neovide_confirm_quit = true If set to `true`, quitting while having unsaved changes will require confirmation. Enabled by default. +#### Detach On Quit + +Possible values are `always_quit`, `always_detach`, or `prompt`. Set to `prompt` by default. + +VimScript: + +```vim +let g:neovide_detach_on_quit = 'always_quit' +``` + +Lua: + +```lua +vim.g.neovide_detach_on_quit = 'always_quit' +``` + +This option changes the closing behavior of Neovide when it's used to connect to a remote Neovim +instance. It does this by switching between detaching from the remote instance and quitting Neovim +entirely. + #### Fullscreen VimScript: diff --git a/website/docs/editing-with-external-tools.md b/website/docs/editing-with-external-tools.md index 7cfd408021..304f47f6e2 100644 --- a/website/docs/editing-with-external-tools.md +++ b/website/docs/editing-with-external-tools.md @@ -11,7 +11,7 @@ your own responsibility._ In your configuration file: ```yaml -editor: "neovide --no-fork" +editor: "neovide" ``` ...as `jrnl` saves & removes the temporary file as soon as the main process exits, which happens diff --git a/website/docs/installation.md b/website/docs/installation.md index c1bfa24af3..8099b68fa9 100644 --- a/website/docs/installation.md +++ b/website/docs/installation.md @@ -140,16 +140,6 @@ Just add `neovide` from nixpkgs to your `environment.systemPackages` in `configu environment.systemPackages = with pkgs; [neovide]; ``` -### Snap - -Neovide is also available in the Snap Store. You can install it using the command below. - -```sh -snap install neovide -``` - -[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/neovide) - ### Linux Source 1. Install necessary dependencies (adjust for your preferred package manager, probably most of this diff --git a/website/docs/troubleshooting.md b/website/docs/troubleshooting.md index e089895b5e..b7557105a0 100644 --- a/website/docs/troubleshooting.md +++ b/website/docs/troubleshooting.md @@ -83,7 +83,7 @@ be very fast. If not, it will build Neovide first. You have to specify ```sh cd [neovide-source-dir] - cargo run --profile profiling --features profiling -- --no-fork [neovide-arguments...] + cargo run --profile profiling --features profiling -- [neovide-arguments...] ``` Now do whatever leads to performance issue in Neovide and exit.