commit 46797c26a36efd3de50f6f6068c28e87b5443931 Author: Stephen Waits Date: Mon Dec 23 17:18:54 2024 -0700 feat: initial commit diff --git a/.build.yml b/.build.yml new file mode 100644 index 0000000..37007f0 --- /dev/null +++ b/.build.yml @@ -0,0 +1,27 @@ +image: alpine/edge +packages: + - git + +secrets: + - 88cce185-d2ec-4573-9546-e33f36f79bbf + +environment: + # repo and user names on both sr.ht and github + REPO: universal-clipboard.nvim + USER: swaits + +tasks: + - fetch-all-branches: | + cd "$HOME/$REPO" + git fetch origin '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' --prune + + - get-github-public-key: | + ssh-keyscan -t rsa github.com >> "$HOME/.ssh/known_hosts" + + - add-github-remote: | + cd "$HOME/$REPO" + git remote add github "git@github.com:$USER/$REPO.git" + + - mirror-to-github: | + cd "$HOME/$REPO" + git push --mirror github diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35d39a7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Stephen Waits + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b55a83e --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# universal-clipboard.nvim + +A **minimal** Neovim plugin that automatically sets up sharing your **system +clipboard** across multiple platforms. It addresses two common issues: + +1. **Platform Differences**: On macOS, `pbcopy/pbpaste` is native; on Linux, + there’s `wl-copy`/`wl-paste` for Wayland or `xclip`/`xsel` for X11. +2. **Missing `+clipboard`**: Many distro packages ship Neovim **without** + built-in clipboard support. + +With **universal-clipboard.nvim**: + +- If Neovim **has** `+clipboard`, we simply use `unnamedplus`. +- If **not**, we detect which clipboard tool (`wl-clipboard`, `xclip`, `xsel`) + is available, and configure a fallback automatically. + +## Installation + +Using [**packer.nvim**](https://github.com/wbthomason/packer.nvim): + +```lua +use({ + "swaits/universal-clipboard.nvim", + config = function() + require("universal-clipboard").setup({ + verbose = false, -- optional, set true to see a small startup message + }) + end, +}) +``` + +Or [**lazy.nvim**](https://github.com/folke/lazy.nvim): + +```lua +{ + "swaits/universal-clipboard.nvim", + opts = { + verbose = true, -- optional, set true to see a small startup message + }, +} +``` + +### Default Options + +```lua +{ + -- If true, prints the chosen clipboard mode at startup + verbose = false, +} +``` + +## Usage + +Once installed, **no additional steps** are needed. The plugin configures your +clipboard on startup. Simply copy and paste in Neovim as usual. + +> **Note:** If your Neovim lacks `+clipboard`, make sure you have at least one +> of `wl-clipboard`, `xclip`, or `xsel` is installed. + +### Commands + +- `:UniversalClipboardCheck` - Shows whether Neovim has `+clipboard` and the + current fallback configuration, if any. +- `:UniversalClipboardReinit` - Re-runs detection. You **do not need to run + this.** It's run automatically on startup. + +## License + +[MIT License](LICENSE) diff --git a/lua/universal-clipboard/init.lua b/lua/universal-clipboard/init.lua new file mode 100644 index 0000000..740cfa5 --- /dev/null +++ b/lua/universal-clipboard/init.lua @@ -0,0 +1,119 @@ +-- lua/universal-clipboard/init.lua +local M = {} + +-- 1) Default options +local default_opts = { + verbose = false, +} + +-- 2) Check if Neovim has '+clipboard' +local function has_builtin_clipboard() + return vim.fn.has("clipboard") == 1 +end + +-- 3) Pick copy/paste commands for Wayland or X11 if '+clipboard' is missing +local function pick_copy_paste_commands() + -- Check Wayland + wl-copy/wl-paste + local wayland_display = os.getenv("WAYLAND_DISPLAY") + local wayland_runtime = os.getenv("XDG_RUNTIME_DIR") + local wayland_socket_path = (wayland_runtime or "") .. "/" .. (wayland_display or "") + + local has_wlcopy = (vim.fn.executable("wl-copy") == 1) + local has_wlpaste = (vim.fn.executable("wl-paste") == 1) + local have_wayland = wayland_display and wayland_display ~= "" and (vim.fn.isdirectory(wayland_socket_path) == 1) + + if has_wlcopy and has_wlpaste and have_wayland then + return { + copy = "wl-copy", + paste = "wl-paste --no-newline", + } + end + + -- Otherwise, try xclip + if vim.fn.executable("xclip") == 1 then + return { + copy = "xclip -selection clipboard", + paste = "xclip -selection clipboard -o", + } + end + + -- Otherwise, try xsel + if vim.fn.executable("xsel") == 1 then + return { + copy = "xsel --clipboard --input", + paste = "xsel --clipboard --output", + } + end + + -- No suitable tool found + return nil +end + +-- 4) Actually configure Neovim's clipboard usage +local function configure_clipboard() + if has_builtin_clipboard() then + -- Neovim has native +clipboard + vim.opt.clipboard = "unnamedplus" + return "builtin" + else + -- Fall back to custom clipboard provider + local commands = pick_copy_paste_commands() + if commands then + vim.g.clipboard = { + name = "UniversalClipboard", + copy = { + ["+"] = commands.copy, + ["*"] = commands.copy, + }, + paste = { + ["+"] = commands.paste, + ["*"] = commands.paste, + }, + cache_enabled = 0, + } + -- Also unify normal yanks/pastes to system clipboard + vim.opt.clipboard = "unnamedplus" + return "fallback" + else + -- No tool found + vim.notify( + "[universal-clipboard.nvim] No suitable clipboard tool found (wl-copy, xclip, xsel).", + vim.log.levels.WARN + ) + return "none" + end + end +end + +-- 5) Setup function (entry point) +function M.setup(user_opts) + -- Merge defaults with user_opts + local opts = vim.tbl_deep_extend("force", default_opts, user_opts or {}) + + local mode = configure_clipboard() + if opts.verbose then + vim.notify(("[universal-clipboard.nvim] Clipboard mode: %s"):format(mode), vim.log.levels.INFO) + end + + -- Create user commands for debugging / re-init + vim.api.nvim_create_user_command("UniversalClipboardCheck", function() + if has_builtin_clipboard() then + print("Neovim has +clipboard. Current setting:") + print(vim.inspect(vim.opt.clipboard:get())) + else + print("Neovim lacks +clipboard. Using fallback if available.") + if vim.g.clipboard then + print("Fallback config: " .. vim.inspect(vim.g.clipboard)) + else + print("No fallback is set (no suitable tool found?).") + end + end + end, {}) + + vim.api.nvim_create_user_command("UniversalClipboardReinit", function() + local new_mode = configure_clipboard() + print("Reinitialized clipboard mode: " .. new_mode) + end, {}) +end + +return M