diff --git a/.config/nvim/lua/config/autocmds.lua b/.config/nvim/lua/config/autocmds.lua index 727a040..ccb42f8 100644 --- a/.config/nvim/lua/config/autocmds.lua +++ b/.config/nvim/lua/config/autocmds.lua @@ -207,3 +207,16 @@ vim.api.nvim_create_autocmd("ModeChanged", { vim.wo.cursorline = vim.api.nvim_get_mode().mode == "nt" end, }) + + +-- Always use insert mode when entering a terminal buffer, even with mouse click. +-- NOTE: Clicking with a mouse a second time enters visual select mode, just like in a text buffer. +vim.api.nvim_create_autocmd({ "BufEnter", "WinEnter" }, { + pattern = "*", + callback = function() + if vim.bo.buftype == "terminal" then + vim.cmd("startinsert") + end + end, +}) + diff --git a/.config/nvim/lua/plugins/r-nvim.lua b/.config/nvim/lua/plugins/r-nvim.lua new file mode 100644 index 0000000..b07be86 --- /dev/null +++ b/.config/nvim/lua/plugins/r-nvim.lua @@ -0,0 +1,65 @@ +-- Integration with a running R interpreter +return { + "R-nvim/R.nvim", + + -- Current latest version (0.99.5) doesn't fully support old R versions. + version="0.99.4", + + -- Only load the plugin if we can find an executable for R. + cond = function() + return vim.fn.executable("R") == 1 + end, + + opts = { + -- These support syntax highlighting in the R interpreter + Rout_more_colors=true, + Rout_follow_colorscheme=true, + -- config_tmux=false, + rconsole_width=80, + + -- by default, :RDSendLine will send the entire block (entire for-loop or entire function, for example). Disabling this sends just one line at a time. + parenblock=false, + min_editor_width=18, + view_df = { + -- open a dataframe with rv in visidata in a new nvim terminal window. + -- Note: using tmux as per the docs does not work when on an interactive + -- node, so we use local terminal here. + open_app = "terminal:vd", + }, + r_ls = { + -- Completion lists are created by the built-in language server per + -- package, per version. It can take a while to build completion lists, + -- especially when you bounce between environments. They are cached + -- though, so it's a one-time cost. If you're OK paying that cost for + -- convenient completion, set completion = true (which is the default) + completion = false, + }, + + hook = { + on_filetype = function() + vim.api.nvim_buf_set_keymap(0, "n", "gxx", "RDSendLine", {}) + vim.api.nvim_buf_set_keymap(0, "v", "gx", "RSendSelection", {}) + vim.api.nvim_buf_set_keymap(0, "n", ",cd", "RDSendChunk", {}) + vim.api.nvim_buf_set_keymap(0, "n", "k", "RSend rmarkdown::render('%p')", {}) + vim.keymap.set("n", "k", + function() + local path = vim.api.nvim_buf_get_name(0) + require("r.send").cmd('rmarkdown::render("' .. path .. '")') + end, + { buffer = true, desc = "Render Rmarkdown" }) + + -- In 0.99.4 which is used here for compatibility with older + -- R versions, the RDSendLine command sends the entire treesitter block + -- (entire function, entire for-loop, etc) rather than just one line. + -- This is fixed in 0.99.5, but patching here so we can use it with 0.99.4. + -- The behavior is still accessible with the default d. + vim.keymap.set("n", "gxx", function() + local line = vim.api.nvim_get_current_line() + require("r.send").cmd(line) + vim.cmd("normal! j") + end, { buffer = true }) + end, + }, + }, + lazy=false +} diff --git a/.config/nvim/lua/plugins/toggleterm.lua b/.config/nvim/lua/plugins/toggleterm.lua index a1121f6..bb3521c 100644 --- a/.config/nvim/lua/plugins/toggleterm.lua +++ b/.config/nvim/lua/plugins/toggleterm.lua @@ -18,15 +18,6 @@ return { end, }) - -- Always use insert mode when entering a terminal buffer, even with mouse click. - -- NOTE: Clicking with a mouse a second time enters visual select mode, just like in a text buffer. - vim.api.nvim_create_autocmd("BufEnter", { - pattern = "*", - callback = function() - vim.cmd("if &buftype == 'terminal' | startinsert | endif") - end, - }) - -- Patch toggleterm to use bracketed paste (special escape codes before -- and after the text to be pasted) -- https://en.wikipedia.org/wiki/Bracketed-paste diff --git a/.functions b/.functions index 7ad8841..2f8d9ac 100644 --- a/.functions +++ b/.functions @@ -121,3 +121,90 @@ function git-clean-branches () { echo "Use 'git-clean-branches | xargs git branch -d' to actually delete these." >&2 } + + + +# nvimcom is the R package that the Neovim plugin R.nvim uses to talk to +# a running R session. It's a compiled package, so it must be built against the +# exact R version in use. +# +# To support multiple R versions side by side, we install it into a per-version +# "sidecar" library under ~/R/nvimcom/ rather than into R's default +# user library. The correct nvimcom package is added to R_LIBS_USER with the +# Ropen function. +# +# In this way, we can use R.nvim with any version of R without installing it +# into the respective R's environment. + +# Echo the X.Y.Z version of the R currently on PATH (empty + nonzero on failure). +_r_version() { + command -v R &>/dev/null || return 1 + R --version 2>/dev/null | grep -Eom1 '[0-9]+\.[0-9]+\.[0-9]+' +} + +install-nvimcom() { + local nvimcom_src bundled_version r_version sidecar_lib installed_desc install_log min_r + + # nvimcom's source ships inside the R.nvim plugin (installed via lazy.nvim). + nvimcom_src="$HOME/.local/share/nvim/lazy/R.nvim/nvimcom" + [[ -d "$nvimcom_src" ]] || { + echo "ERROR: nvimcom source not found at $nvimcom_src." + return 1 + } + + r_version=$(_r_version) || { + echo "ERROR: No R on PATH." + return 1 + } + bundled_version=$(awk '/^Version:/{print $2}' "$nvimcom_src/DESCRIPTION") + sidecar_lib="$HOME/R/nvimcom/$r_version" + installed_desc="$sidecar_lib/nvimcom/DESCRIPTION" + + # Refuse to build against an R that's too old. nvimcom is compiled C code; + # a too-old R compiles fine (C only warns on implicit declarations) but + # fails at load with a cryptic "undefined symbol" error. Catch it here + # instead by honoring the minimum R version from nvimcom's DESCRIPTION. + min_r=$(grep -oE 'R \(>= [0-9]+\.[0-9]+(\.[0-9]+)?\)' "$nvimcom_src/DESCRIPTION" \ + | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?') + if [[ -n "$min_r" ]] && + [[ "$(printf '%s\n%s\n' "$min_r" "$r_version" | sort -V | head -1)" != "$min_r" ]]; then + echo "ERROR: nvimcom $bundled_version requires R >= $min_r, but R $r_version is on PATH." + echo " Put a newer R on PATH, or pin an older R.nvim/nvimcom." + return 1 + fi + + # Skip the build if the sidecar already has the matching version installed. + if [[ -f "$installed_desc" ]] && + [[ "$(awk '/^Version:/{print $2}' "$installed_desc")" == "$bundled_version" ]]; then + return 0 + fi + + # Build into the sidecar. R_LIBS_USER is set so build-time dependency + # lookups also resolve against the sidecar. + echo "Installing nvimcom $bundled_version for R $r_version ..." + mkdir -p "$sidecar_lib" + install_log="/tmp/nvimcom-install-$r_version.log" + if R_LIBS_USER="$sidecar_lib" R CMD INSTALL --library="$sidecar_lib" "$nvimcom_src" >"$install_log" 2>&1; then + echo "✓ nvimcom $bundled_version installed for R $r_version." + else + echo "✗ Failed — see $install_log" + return 1 + fi +} + +Ropen() { + # Opens $EDITOR (Neovim) for an R session, making sure the version-matched + # nvimcom sidecar library exists and is on R_LIBS_USER so R.nvim can use it. + local r_version sidecar_lib + r_version=$(_r_version) || { + echo "No R found on PATH." + return 1 + } + + install-nvimcom || return 1 + + # Prepend the sidecar to R_LIBS_USER (keeping the normal user library too). + sidecar_lib="$HOME/R/nvimcom/$r_version" + R_LIBS_USER="$sidecar_lib:${R_LIBS_USER:-$HOME/R/library}" $EDITOR "$@" +} + diff --git a/docs/changelog.rst b/docs/changelog.rst index 6dfeb06..4716010 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,12 +1,24 @@ Changelog ========= +2026-06-14 +---------- + +**vim** + +Add experimental R.nvim plugin support, including additional functions +``Ropen`` and ``install-nvimcom`` in :file:`.functions`. + +Currently not fully functional due to tab completion database resource usage. + + 2026-05-28 ---------- **vim** - Add [pasteimg](https://github.com/daler/pasteimg.nvim) plugin +- Remove ``img-clip`` plugin (which didn't work on remote systems) - Improve toggleterm "non-insert mode" cursorline: - use the Comment color of the initially-loaded colorscheme instead of a hard-coded red - don't clobber the custom background that toggleterm sets diff --git a/docs/nvim-plugins.rst b/docs/nvim-plugins.rst index 1e25d15..ee70a10 100644 --- a/docs/nvim-plugins.rst +++ b/docs/nvim-plugins.rst @@ -1508,10 +1508,134 @@ See the docs for the plugin on how to set it up, including a macOS Shortcut. .. plugin-metadata:: :name: pasteimg -.. _imgclip_ref: +.. _rnvim_ref: +``r.nvim`` +~~~~~~~~~~ + +`R.nvim `__ provides tight integration with R running in an nvim terminal. + +.. warning:: + + **This is experimental.** + + Currently the biggest issue is the creation of the tab-completion database. + ``nvimcom`` does this at startup, and with no apparent working way of + disabling it on the nvim side. It does this for *each installed R library*. + If you use this on an all-in-one installation, like the one for Biowulf's + ``module load R``, it will consume all resources on the node. + + For something like a project-specific R environment, it does take a couple + minutes the first time on a big node, but using the same env after that is + instant. + + Another issue is support for old R versions. Currently, R.nvim v0.99.4 + cleanly supports old R versions but 0.99.5 does not (despite release notes + to the contrary). This will likely change since the plugin is under active + development. + + +This provides more features than simply running R in an nvim terminal (like +with :ref:`toggleterm_ref`). + +- R object browser (like the panel in RStudio) +- quickly view a dataframe in Visidata (like``View()`` in RStudio) +- tab-completion of functions and arguments +- syntax highlighting in the R interpreter +- R documentation within text buffer +- An R language server that doesn't itself require R (specifically, it doesn't + require being installed into the same environment as R) +- More flexibility in controlling what is sent to the terminal +- Insert commented version of output into the text buffer (great for + ``head(df)`` so your comments reflect what's in the data.frame) +- Send ``str`` with the name of the object under the cursor for quick inspection in R +- Jump through an RMarkdown file by chunk +- Lots more... + +In order for this to work, the ``nvimcom`` package needs to be installed into +the environment with R. However, it didn't seem right to "contaminate" an +otherwise reproducible R environment with a package just for personal editing +preferences. + +To use this additional functionality, you need to do the following two steps: + +- Activate the environment you want to use, on the host you want to use. If + you're running R on an interactive node, you need to be *running nvim on the + interactive node*. +- Use ``Ropen `` to start nvim with the right env vars set. This + function is now included in the updated :file:`.functions` file of these + dotfiles. It also uses the ``install-nvimcom`` function from that file. + + +.. details:: What these functions do + + ``nvimcom`` is compiled. The source code lives in this nvim plugin, but it + needs to be compiled on the R version you're using. The trick here is that + we're compiling it the first time you use it for an R version, but storing it + in a "sidecar" library. + + ``Ropen`` checks the + version of R on the path, pulls the source from the nvim plugin directory, + compiles nvimcom if it needs to using that R and stores it in + :file:`~/R/nvimcom/` (that's the sidecar library), sets the + ``R_LIBS_USER`` env var to point to that sidecar library, and launches R. + +.. note:: + + R.nvim uses ```` instead of ````. This essentially + allows another whole namespace to put shortcuts. + + By default ```` is ``\\`` (backslash). + + Commands that do the same thing as :ref:`toggleterm_ref` (``gx``, ``gxx``, + ``,cd``, ``,k``) have been remapped to match existing toggleterm so you can + take advantage of existing muscle memory. +======= + +See the docs for the plugin on how to set it up, including a macOS Shortcut. + +.. list-table:: + :header-rows: 1 + :align: left + + * - command + - description + + * - ``rf`` + - Starts an R session that communicates with nvimcom + + * - ``gxx`` on a line + - Send line to R (like :ref:`toggleterm_ref`) + + * - ``gx`` on a selection + - Send selection to R (like :ref:`toggleterm_ref`) + + * - ``cd`` + - Send chunk to R (like :ref:`toggleterm_ref`) + + * - ``k`` + - Render RMarkdown to HTML (like :ref:`toggleterm_ref`) + + * - ``o`` + - Open R object browser + + * - ``rv`` with the cursor on a dataframe variable + - Open a new nvim terminal to view the data frame in visidata. ``q`` + quits visidata and the terminal. + + * - ``ro`` + - Run the line under the cursor in R, capture the output, and paste it + under the cursor -- but commented out. Useful for documenting data + structures. + + * - ``:RMapsDesc`` + - List all the other commands possible + + +.. plugin-metadata:: + :name: r-nvim -.. colorschemes_ref: +.. _colorschemes_ref: Colorschemes ------------