diff --git a/lua/r/browser.lua b/lua/r/browser.lua index ff8417a0..183c2e1c 100644 --- a/lua/r/browser.lua +++ b/lua/r/browser.lua @@ -20,8 +20,9 @@ with the R backend and update the Object Browser interface accordingly. ]] local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn local job = require("r.job") +local hooks = require("r.hooks") local send_to_nvimcom = require("r.run").send_to_nvimcom -- Determine if the locale uses UTF-8 encoding @@ -332,10 +333,7 @@ function M.start(_) start_OB() state.is_running = false - -- Execute any user-defined hooks after opening the Object Browser - if config.hook.after_ob_open then - vim.schedule(function() config.hook.after_ob_open() end) - end + hooks.run(config, "after_ob_open") end --- Return the active pane of the Object Browser diff --git a/lua/r/commands.lua b/lua/r/commands.lua new file mode 100644 index 00000000..9e03c8cb --- /dev/null +++ b/lua/r/commands.lua @@ -0,0 +1,82 @@ +local config = require("r.config").get_config() + +local M = {} + +local function show_config(tbl) + local opt = tbl.args + local out = {} + if opt and opt:len() > 0 then + opt = opt:gsub(" .*", "") + table.insert(out, { vim.inspect(config[opt]) }) + else + table.insert(out, { vim.inspect(config) }) + end + vim.schedule(function() vim.api.nvim_echo(out, false, {}) end) +end + +local config_keys = {} +for k, _ in pairs(config) do + table.insert(config_keys, tostring(k)) +end + +function M.create_user_commands() + vim.api.nvim_create_user_command( + "RStop", + function(_) require("r.run").signal_to_R("SIGINT") end, + {} + ) + vim.api.nvim_create_user_command( + "RKill", + function(_) require("r.run").signal_to_R("SIGKILL") end, + {} + ) + vim.api.nvim_create_user_command("RBuildTags", require("r.edit").build_tags, {}) + vim.api.nvim_create_user_command("RDebugInfo", require("r.edit").show_debug_info, {}) + vim.api.nvim_create_user_command("RMapsDesc", require("r.maps").show_map_desc, {}) + + vim.api.nvim_create_user_command( + "RSend", + function(tbl) require("r.send").cmd(tbl.args) end, + { nargs = 1 } + ) + + vim.api.nvim_create_user_command( + "RFormat", + require("r.run").formart_code, + { range = "%" } + ) + + vim.api.nvim_create_user_command( + "RInsert", + function(tbl) require("r.run").insert(tbl.args, "here") end, + { nargs = 1 } + ) + + vim.api.nvim_create_user_command( + "RSourceDir", + function(tbl) require("r.run").source_dir(tbl.args) end, + { nargs = 1, complete = "dir" } + ) + + vim.api.nvim_create_user_command( + "RHelp", + function(tbl) require("r.doc").ask_R_help(tbl.args) end, + { + nargs = "?", + complete = require("r.server").list_objs, + } + ) + + vim.api.nvim_create_user_command("RConfigShow", show_config, { + nargs = "?", + complete = function() return config_keys end, + }) + + vim.api.nvim_create_user_command( + "Roxygenize", + function() require("r.roxygen").insert_roxygen(vim.api.nvim_get_current_buf()) end, + {} + ) +end + +return M diff --git a/lua/r/config.lua b/lua/r/config.lua index a8b14022..8704001e 100644 --- a/lua/r/config.lua +++ b/lua/r/config.lua @@ -1,5 +1,7 @@ -local warn = require("r").warn -local uv = vim.loop +local warn = require("r.log").warn +local utils = require("r.utils") +local uv = vim.uv +local hooks = require("r.hooks") -- stylua: ignore start @@ -111,22 +113,10 @@ local config = { -- stylua: ignore end -local config_keys - local user_opts = {} local did_real_setup = false - -local show_config = function(tbl) - local opt = tbl.args - local out = {} - if opt and opt:len() > 0 then - opt = opt:gsub(" .*", "") - table.insert(out, { vim.inspect(config[opt]) }) - else - table.insert(out, { vim.inspect(config) }) - end - vim.schedule(function() vim.api.nvim_echo(out, false, {}) end) -end +local unix = require("r.platform.unix") +local windows = require("r.platform.windows") local set_editing_mode = function() if config.editing_mode ~= "" then return end @@ -170,8 +160,6 @@ end --- being applied. If a check fails, a warning is show, and the default option --- is used instead. local apply_user_opts = function() - local utils = require("r.utils") - -- Ensure that some config options will be in lower case for _, v in pairs({ "auto_start", @@ -295,8 +283,6 @@ local apply_user_opts = function() end local do_common_global = function() - local utils = require("r.utils") - config.uname = uv.os_uname().sysname config.is_windows = config.uname:find("Windows", 1, true) ~= nil config.is_darwin = config.uname == "Darwin" @@ -586,261 +572,11 @@ local do_common_global = function() end end -local resolve_fullpaths = function(tbl) - for i, v in ipairs(tbl) do - tbl[i] = uv.fs_realpath(v) - end -end - -local windows_config = function() - local utils = require("r.utils") - local wtime = uv.hrtime() - local isi386 = false - - if config.R_path ~= "" then - local rpath = vim.split(config.R_path, ";") - resolve_fullpaths(rpath) - vim.fn.reverse(rpath) - for _, dir in ipairs(rpath) do - if vim.fn.isdirectory(dir) then - vim.env.PATH = dir .. ";" .. vim.env.PATH - else - warn( - '"' - .. dir - .. '" is not a directory. Fix the value of R_path in your config.' - ) - end - end - else - if vim.env.RTOOLS40_HOME then - if vim.fn.isdirectory(vim.env.RTOOLS40_HOME .. "\\mingw64\\bin\\") then - vim.env.PATH = vim.env.RTOOLS40_HOME .. "\\mingw64\\bin;" .. vim.env.PATH - elseif vim.fn.isdirectory(vim.env.RTOOLS40_HOME .. "\\usr\\bin") then - vim.env.PATH = vim.env.RTOOLS40_HOME .. "\\usr\\bin;" .. vim.env.PATH - end - else - if vim.fn.isdirectory("C:\\rtools40\\mingw64\\bin") then - vim.env.PATH = "C:\\rtools40\\mingw64\\bin;" .. vim.env.PATH - elseif vim.fn.isdirectory("C:\\rtools40\\usr\\bin") then - vim.env.PATH = "C:\\rtools40\\usr\\bin;" .. vim.env.PATH - end - end - - local get_rip = function(run_cmd) - local resp = utils.system(run_cmd, { text = true }):wait() - local rout = vim.split(resp.stdout, "\n") - local rip = {} - for _, v in pairs(rout) do - if v:find("InstallPath.*REG_SZ") then table.insert(rip, v) end - end - return rip - end - - -- Check both HKCU and HKLM. See #223 - local reg_roots = { "HKCU", "HKLM" } - local rip = {} - for i = 1, #reg_roots do - if #rip == 0 then - local run_cmd = - { "reg.exe", "QUERY", reg_roots[i] .. "\\SOFTWARE\\R-core\\R", "/s" } - rip = get_rip(run_cmd) - - if #rip == 0 then - -- Normally, 32 bit applications access only 32 bit registry and... - -- We have to try again if the user has installed R only in the other architecture. - if vim.fn.has("win64") then - table.insert(run_cmd, "/reg:64") - else - table.insert(run_cmd, "/reg:32") - end - rip = get_rip(run_cmd) - - if #rip == 0 and i == #reg_roots then - warn( - "Could not find R path in Windows Registry. " - .. "If you have already installed R, please, set the value of 'R_path'." - ) - wtime = (uv.hrtime() - wtime) / 1000000000 - require("r.edit").add_to_debug_info( - "windows setup", - wtime, - "Time" - ) - return - end - end - end - end - - local rinstallpath = nil - rinstallpath = rip[1] - rinstallpath = rinstallpath:gsub(".*InstallPath.*REG_SZ%s*", "") - rinstallpath = rinstallpath:gsub("\n", "") - rinstallpath = rinstallpath:gsub("%s*$", "") - local hasR32 = vim.fn.isdirectory(rinstallpath .. "\\bin\\i386") - local hasR64 = vim.fn.isdirectory(rinstallpath .. "\\bin\\x64") - if hasR32 == 1 and hasR64 == 0 then isi386 = true end - if hasR64 == 1 and hasR32 == 0 then isi386 = false end - if hasR32 == 1 and isi386 then - vim.env.PATH = rinstallpath .. "\\bin\\i386;" .. vim.env.PATH - elseif hasR64 == 1 and not isi386 then - vim.env.PATH = rinstallpath .. "\\bin\\x64;" .. vim.env.PATH - else - vim.env.PATH = rinstallpath .. "\\bin;" .. vim.env.PATH - end - end - - if not config.R_args then - if type(config.external_term) == "boolean" and config.external_term == false then - config.R_args = { "--no-save" } - else - config.R_args = { "--sdi", "--no-save" } - end - end - wtime = (uv.hrtime() - wtime) / 1000000000 - require("r.edit").add_to_debug_info("windows setup", wtime, "Time") -end - -local tmux_config = function() - local ttime = uv.hrtime() - -- Check whether Tmux is OK - if vim.fn.executable("tmux") == 0 then - config.external_term = false - warn("tmux executable not found") - return - end - - local tmuxversion - if config.uname:find("OpenBSD") then - -- Tmux does not have -V option on OpenBSD: https://github.com/jcfaria/Vim-R-plugin/issues/200 - tmuxversion = "0.0" - else - tmuxversion = vim.fn.system("tmux -V") - if tmuxversion then - tmuxversion = tmuxversion:gsub(".* ([0-9]%.[0-9]).*", "%1") - if #tmuxversion ~= 3 then tmuxversion = "1.0" end - if tmuxversion < "3.0" then warn("R.nvim requires Tmux >= 3.0") end - end - end - ttime = (uv.hrtime() - ttime) / 1000000000 - require("r.edit").add_to_debug_info("tmux setup", ttime, "Time") -end - -local unix_config = function() - local utime = uv.hrtime() - if config.R_path ~= "" then - local rpath = vim.split(config.R_path, ":") - resolve_fullpaths(rpath) - - -- Add the current directory to the beginning of the path - table.insert(rpath, 1, "") - - -- loop over rpath in reverse. - for i = #rpath, 1, -1 do - local dir = rpath[i] - local is_dir = uv.fs_stat(dir) - -- Each element in rpath must exist and be a directory - if is_dir and is_dir.type == "directory" then - vim.env.PATH = dir .. ":" .. vim.env.PATH - else - warn( - '"' - .. dir - .. '" is not a directory. Fix the value of R_path in your config.' - ) - end - end - end - - if vim.fn.executable(config.R_app) ~= 1 then - warn( - '"' - .. config.R_app - .. '" not found. Fix the value of either R_path or R_app in your config.' - ) - end - - if - (type(config.external_term) == "boolean" and config.external_term) - or type(config.external_term) == "string" - then - tmux_config() -- Consider removing this line if it's not necessary - end - utime = (uv.hrtime() - utime) / 1000000000 - require("r.edit").add_to_debug_info("unix setup", utime, "Time") -end - -local create_user_commands = function() - vim.api.nvim_create_user_command( - "RStop", - function(_) require("r.run").signal_to_R("SIGINT") end, - {} - ) - vim.api.nvim_create_user_command( - "RKill", - function(_) require("r.run").signal_to_R("SIGKILL") end, - {} - ) - vim.api.nvim_create_user_command("RBuildTags", require("r.edit").build_tags, {}) - vim.api.nvim_create_user_command("RDebugInfo", require("r.edit").show_debug_info, {}) - vim.api.nvim_create_user_command("RMapsDesc", require("r.maps").show_map_desc, {}) - - vim.api.nvim_create_user_command( - "RSend", - function(tbl) require("r.send").cmd(tbl.args) end, - { nargs = 1 } - ) - - vim.api.nvim_create_user_command( - "RFormat", - require("r.run").formart_code, - { range = "%" } - ) - - vim.api.nvim_create_user_command( - "RInsert", - function(tbl) require("r.run").insert(tbl.args, "here") end, - { nargs = 1 } - ) - - vim.api.nvim_create_user_command( - "RSourceDir", - function(tbl) require("r.run").source_dir(tbl.args) end, - { nargs = 1, complete = "dir" } - ) - - vim.api.nvim_create_user_command( - "RHelp", - function(tbl) require("r.doc").ask_R_help(tbl.args) end, - { - nargs = "?", - complete = require("r.server").list_objs, - } - ) - - vim.api.nvim_create_user_command("RConfigShow", show_config, { - nargs = "?", - complete = function() return config_keys end, - }) - - vim.api.nvim_create_user_command( - "Roxygenize", - function() require("r.roxygen").insert_roxygen(vim.api.nvim_get_current_buf()) end, - {} - ) -end - local global_setup = function() local gtime = uv.hrtime() if vim.g.R_Nvim_status == 0 then vim.g.R_Nvim_status = 1 end - config_keys = {} - for k, _ in pairs(config) do - table.insert(config_keys, tostring(k)) - end - set_pdf_viewer() apply_user_opts() @@ -861,9 +597,9 @@ local global_setup = function() -- See https://github.com/jalvesaq/Nvim-R/issues/625 do_common_global() if config.is_windows then - windows_config() + windows.configure(config) else - unix_config() + unix.configure(config) end -- Override default config values with user options for the second time. @@ -871,13 +607,11 @@ local global_setup = function() config[k] = v end - create_user_commands() + require("r.commands").create_user_commands() vim.fn.timer_start(1, require("r.config").check_health) vim.schedule(function() require("r.server").check_nvimcom_version() end) - if config.hook.after_config then - vim.schedule(function() config.hook.after_config() end) - end + hooks.run(config, "after_config") gtime = (uv.hrtime() - gtime) / 1000000000 require("r.edit").add_to_debug_info("global setup", gtime, "Time") @@ -910,9 +644,7 @@ M.real_setup = function() did_real_setup = true global_setup() end - if config.hook.on_filetype then - vim.schedule(function() config.hook.on_filetype() end) - end + hooks.run(config, "on_filetype") require("r.rproj").apply_settings(config) if config.register_treesitter then @@ -935,12 +667,20 @@ M.check_health = function() warn("Please, uninstall Nvim-R before using R.nvim.") end - if vim.fn.executable(config.R_app) == 0 then - warn("R_app executable not found: '" .. config.R_app .. "'") - end + -- Check R_app asynchronously + utils.check_executable(config.R_app, function(exists) + if not exists then + warn("R_app executable not found: '" .. config.R_app .. "'") + end + end) - if not config.R_cmd == config.R_app and vim.fn.executable(config.R_cmd) == 0 then - warn("R_cmd executable not found: '" .. config.R_cmd .. "'") + -- Check R_cmd asynchronously if it's different from R_app + if config.R_cmd ~= config.R_app then + utils.check_executable(config.R_cmd, function(exists) + if not exists then + warn("R_cmd executable not found: '" .. config.R_cmd .. "'") + end + end) end if vim.fn.has("nvim-0.9.5") ~= 1 then warn("R.nvim requires Neovim >= 0.9.5") end diff --git a/lua/r/doc.lua b/lua/r/doc.lua index 382ae225..4ee6788c 100644 --- a/lua/r/doc.lua +++ b/lua/r/doc.lua @@ -1,6 +1,6 @@ local config = require("r.config").get_config() local send_to_nvimcom = require("r.run").send_to_nvimcom -local warn = require("r").warn +local warn = require("r.log").warn local utils = require("r.utils") local cursor = require("r.cursor") local job = require("r.job") diff --git a/lua/r/edit.lua b/lua/r/edit.lua index f7aa8cef..52dfb6e6 100644 --- a/lua/r/edit.lua +++ b/lua/r/edit.lua @@ -1,6 +1,6 @@ local config = require("r.config").get_config() local get_lang = require("r.utils").get_lang -local warn = require("r").warn +local warn = require("r.log").warn local del_list = {} local rscript_buf = nil local debug_info = { Time = {} } @@ -235,7 +235,7 @@ M.obj = function(fnm) vim.api.nvim_set_option_value("bufhidden", "wipe", { scope = "local" }) vim.cmd("stopinsert") vim.api.nvim_create_autocmd("BufUnload", { - command = "lua vim.loop.fs_unlink('" .. fnm .. "_wait')", + command = "lua vim.uv.fs_unlink('" .. fnm .. "_wait')", pattern = "", }) end) diff --git a/lua/r/external_term.lua b/lua/r/external_term.lua index 4f3f28b1..3359c8f0 100644 --- a/lua/r/external_term.lua +++ b/lua/r/external_term.lua @@ -1,7 +1,7 @@ local config = require("r.config").get_config() local utils = require("r.utils") -local uv = vim.loop -local warn = require("r").warn +local uv=vim.uv +local warn = require("r.log").warn local term_name = nil local term_cmd = nil diff --git a/lua/r/format/brackets.lua b/lua/r/format/brackets.lua index 16f9853d..07b8b7d4 100644 --- a/lua/r/format/brackets.lua +++ b/lua/r/format/brackets.lua @@ -4,7 +4,7 @@ -- Second case, when using the [ operator: vec[1] -> vec[[1]] -- It supports multiple subsetting and nested expressions: df$var[1] -> df[["var"]][[1]] -local warn = require("r").warn +local warn = require("r.log").warn local M = {} local parsers = require("nvim-treesitter.parsers") diff --git a/lua/r/format/numbers.lua b/lua/r/format/numbers.lua index a12d9710..97677f96 100644 --- a/lua/r/format/numbers.lua +++ b/lua/r/format/numbers.lua @@ -1,6 +1,6 @@ local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn local M = {} -- We check if treesitter is available in the check_parsers() function of the diff --git a/lua/r/hooks.lua b/lua/r/hooks.lua new file mode 100644 index 00000000..5c3ed862 --- /dev/null +++ b/lua/r/hooks.lua @@ -0,0 +1,35 @@ +--[[ +This module implements functions that manage the execution of +user-defined hooks. These are defined in the `config.hooks` table in +the user configuration: + +defaults = { + ..., + hook = { + on_filetype = function() end, + after_config = function() end, + after_R_start = function() end, + after_ob_open = function() end, + }, + ..., +} + +User documentation for user-defined hooks: section 6.29 +]] + +local M = {} + +--- Run the specified user-defined hook +--- +--- Currently valid `hook_name` values are 'on_filetype', +--- 'after_config', 'after_R_start', 'after_ob_open' +--- +---@param config table +---@param hook_name string +function M.run(config, hook_name) + if config.hook and config.hook[hook_name] then -- Is config.hook check necessary? + vim.schedule(function() config.hook[hook_name]() end) + end +end + +return M diff --git a/lua/r/init.lua b/lua/r/init.lua index 02b51c9c..eeda2054 100644 --- a/lua/r/init.lua +++ b/lua/r/init.lua @@ -1,27 +1,5 @@ local M = {} ---- Call vim.notify() with a warning message ----@param msg string -M.warn = function(msg) - vim.schedule( - function() vim.notify(msg, vim.log.levels.WARN, { title = "R.nvim" }) end - ) -end - ---- Call vim.notify() with to inform a message ----@param msg string -M.inform = function(msg) - vim.schedule( - function() - vim.notify( - msg, - vim.log.levels.INFO, - { title = "R.nvim", hide_from_history = true } - ) - end - ) -end - --- Quick setup: simply store user options ---@param opts table | nil M.setup = function(opts) diff --git a/lua/r/job.lua b/lua/r/job.lua index 2a94fb5b..c2efa967 100644 --- a/lua/r/job.lua +++ b/lua/r/job.lua @@ -3,7 +3,7 @@ local M = {} local jobs = {} -local warn = require("r").warn +local warn = require("r.log").warn -- Structure to keep track of incomplete input data. local incomplete_input = { size = 0, received = 0, str = "" } diff --git a/lua/r/log.lua b/lua/r/log.lua new file mode 100644 index 00000000..6ca49932 --- /dev/null +++ b/lua/r/log.lua @@ -0,0 +1,21 @@ +--- Helper function to call vim.notify() with scheduled execution +---@param msg string +---@param level number +---@param opts table +local function notify(msg, level, opts) + vim.schedule(function() vim.notify(msg, level, opts) end) +end + +local M = {} + +--- Call vim.notify() with a warning message +---@param msg string +M.warn = function(msg) notify(msg, vim.log.levels.WARN, { title = "R.nvim" }) end + +--- Call vim.notify() to inform a message +---@param msg string +M.inform = function(msg) + notify(msg, vim.log.levels.INFO, { title = "R.nvim", hide_from_history = true }) +end + +return M diff --git a/lua/r/maps.lua b/lua/r/maps.lua index bbb297f7..230d121e 100644 --- a/lua/r/maps.lua +++ b/lua/r/maps.lua @@ -1,5 +1,5 @@ local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn -- stylua: ignore start diff --git a/lua/r/osx.lua b/lua/r/osx.lua index e7853031..fc535557 100644 --- a/lua/r/osx.lua +++ b/lua/r/osx.lua @@ -1,5 +1,5 @@ local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn local r64app = nil local M = {} diff --git a/lua/r/packages.lua b/lua/r/packages.lua index 8e477c05..680c6eaf 100644 --- a/lua/r/packages.lua +++ b/lua/r/packages.lua @@ -1,8 +1,8 @@ -local warn = require("r").warn +local warn = require("r.log").warn local M = {} local S = require("r.send") -local inform = require("r").inform +local inform = require("r.log").inform --- Removes duplicate entries from a table of packages. --- Each package is represented by a table with a 'message' field. diff --git a/lua/r/pdf/generic.lua b/lua/r/pdf/generic.lua index 058b72dc..49d92899 100644 --- a/lua/r/pdf/generic.lua +++ b/lua/r/pdf/generic.lua @@ -1,4 +1,4 @@ -local warn = require("r").warn +local warn = require("r.log").warn local config = require("r.config").get_config() local job = require("r.job") diff --git a/lua/r/pdf/init.lua b/lua/r/pdf/init.lua index 3f120995..671d4b3b 100644 --- a/lua/r/pdf/init.lua +++ b/lua/r/pdf/init.lua @@ -1,8 +1,8 @@ local config = require("r.config").get_config() local utils = require("r.utils") -local warn = require("r").warn +local warn = require("r.log").warn local job = require("r.job") -local uv = vim.loop +local uv=vim.uv local check_installed = function() if vim.fn.executable(config.pdfviewer) == 0 then diff --git a/lua/r/pdf/zathura.lua b/lua/r/pdf/zathura.lua index 8afb6926..bcdaf133 100644 --- a/lua/r/pdf/zathura.lua +++ b/lua/r/pdf/zathura.lua @@ -1,4 +1,4 @@ -local warn = require("r").warn +local warn = require("r.log").warn local utils = require("r.utils") local job = require("r.job") diff --git a/lua/r/platform/tmux.lua b/lua/r/platform/tmux.lua new file mode 100644 index 00000000..c4e2fe8e --- /dev/null +++ b/lua/r/platform/tmux.lua @@ -0,0 +1,30 @@ +local M = {} +local uv = vim.uv +local log = require("r.log") + +function M.configure(config) + local ttime = uv.hrtime() + -- Check whether Tmux is OK + if vim.fn.executable("tmux") == 0 then + config.external_term = false + log.warn("tmux executable not found") + return + end + + local tmuxversion + if config.uname:find("OpenBSD") then + -- Tmux does not have -V option on OpenBSD: https://github.com/jcfaria/Vim-R-plugin/issues/200 + tmuxversion = "0.0" + else + tmuxversion = vim.fn.system("tmux -V") + if tmuxversion then + tmuxversion = tmuxversion:gsub(".* ([0-9]%.[0-9]).*", "%1") + if #tmuxversion ~= 3 then tmuxversion = "1.0" end + if tmuxversion < "3.0" then log.warn("R.nvim requires Tmux >= 3.0") end + end + end + ttime = (uv.hrtime() - ttime) / 1000000000 + require("r.edit").add_to_debug_info("tmux setup", ttime, "Time") +end + +return M diff --git a/lua/r/platform/unix.lua b/lua/r/platform/unix.lua new file mode 100644 index 00000000..ba3c1edf --- /dev/null +++ b/lua/r/platform/unix.lua @@ -0,0 +1,54 @@ +local M = {} + +local uv = vim.uv +local log = require("r.log") +local utils = require("r.utils") +local tmux = require("r.platform.tmux") + +function M.configure(config) + local utime = uv.hrtime() + if config.R_path ~= "" then + local rpath = vim.split(config.R_path, ":") + utils.resolve_fullpaths(rpath) + + -- Add the current directory to the beginning of the path + table.insert(rpath, 1, "") + + -- loop over rpath in reverse. + for i = #rpath, 1, -1 do + local dir = rpath[i] + local is_dir = uv.fs_stat(dir) + -- Each element in rpath must exist and be a directory + if is_dir and is_dir.type == "directory" then + vim.env.PATH = dir .. ":" .. vim.env.PATH + else + log.warn( + '"' + .. dir + .. '" is not a directory. Fix the value of R_path in your config.' + ) + end + end + end + + utils.check_executable(config.R_app, function(exists) + if not exists then + log.warn( + '"' + .. config.R_app + .. '" not found. Fix the value of either R_path or R_app in your config.' + ) + end + end) + + if + (type(config.external_term) == "boolean" and config.external_term) + or type(config.external_term) == "string" + then + tmux.configure(config) + end + utime = (uv.hrtime() - utime) / 1000000000 + require("r.edit").add_to_debug_info("unix setup", utime, "Time") +end + +return M diff --git a/lua/r/platform/windows.lua b/lua/r/platform/windows.lua new file mode 100644 index 00000000..c27ffaa9 --- /dev/null +++ b/lua/r/platform/windows.lua @@ -0,0 +1,116 @@ +local M = {} + +local uv = vim.uv +local utils = require("r.utils") +local log = require("r.log") + +function M.configure(config) + local wtime = uv.hrtime() + local isi386 = false + + if config.R_path ~= "" then + local rpath = vim.split(config.R_path, ";") + utils.resolve_fullpaths(rpath) + vim.fn.reverse(rpath) + for _, dir in ipairs(rpath) do + if vim.fn.isdirectory(dir) then + vim.env.PATH = dir .. ";" .. vim.env.PATH + else + log.warn( + '"' + .. dir + .. '" is not a directory. Fix the value of R_path in your config.' + ) + end + end + else + if vim.env.RTOOLS40_HOME then + if vim.fn.isdirectory(vim.env.RTOOLS40_HOME .. "\\mingw64\\bin\\") then + vim.env.PATH = vim.env.RTOOLS40_HOME .. "\\mingw64\\bin;" .. vim.env.PATH + elseif vim.fn.isdirectory(vim.env.RTOOLS40_HOME .. "\\usr\\bin") then + vim.env.PATH = vim.env.RTOOLS40_HOME .. "\\usr\\bin;" .. vim.env.PATH + end + else + if vim.fn.isdirectory("C:\\rtools40\\mingw64\\bin") then + vim.env.PATH = "C:\\rtools40\\mingw64\\bin;" .. vim.env.PATH + elseif vim.fn.isdirectory("C:\\rtools40\\usr\\bin") then + vim.env.PATH = "C:\\rtools40\\usr\\bin;" .. vim.env.PATH + end + end + + local get_rip = function(run_cmd) + local resp = utils.system(run_cmd, { text = true }):wait() + local rout = vim.split(resp.stdout, "\n") + local rip = {} + for _, v in pairs(rout) do + if v:find("InstallPath.*REG_SZ") then table.insert(rip, v) end + end + return rip + end + + -- Check both HKCU and HKLM. See #223 + local reg_roots = { "HKCU", "HKLM" } + local rip = {} + for i = 1, #reg_roots do + if #rip == 0 then + local run_cmd = + { "reg.exe", "QUERY", reg_roots[i] .. "\\SOFTWARE\\R-core\\R", "/s" } + rip = get_rip(run_cmd) + + if #rip == 0 then + -- Normally, 32 bit applications access only 32 bit registry and... + -- We have to try again if the user has installed R only in the other architecture. + if vim.fn.has("win64") then + table.insert(run_cmd, "/reg:64") + else + table.insert(run_cmd, "/reg:32") + end + rip = get_rip(run_cmd) + + if #rip == 0 and i == #reg_roots then + log.warn( + "Could not find R path in Windows Registry. " + .. "If you have already installed R, please, set the value of 'R_path'." + ) + wtime = (uv.hrtime() - wtime) / 1000000000 + require("r.edit").add_to_debug_info( + "windows setup", + wtime, + "Time" + ) + return + end + end + end + end + + local rinstallpath = nil + rinstallpath = rip[1] + rinstallpath = rinstallpath:gsub(".*InstallPath.*REG_SZ%s*", "") + rinstallpath = rinstallpath:gsub("\n", "") + rinstallpath = rinstallpath:gsub("%s*$", "") + local hasR32 = vim.fn.isdirectory(rinstallpath .. "\\bin\\i386") + local hasR64 = vim.fn.isdirectory(rinstallpath .. "\\bin\\x64") + if hasR32 == 1 and hasR64 == 0 then isi386 = true end + if hasR64 == 1 and hasR32 == 0 then isi386 = false end + if hasR32 == 1 and isi386 then + vim.env.PATH = rinstallpath .. "\\bin\\i386;" .. vim.env.PATH + elseif hasR64 == 1 and not isi386 then + vim.env.PATH = rinstallpath .. "\\bin\\x64;" .. vim.env.PATH + else + vim.env.PATH = rinstallpath .. "\\bin;" .. vim.env.PATH + end + end + + if not config.R_args then + if type(config.external_term) == "boolean" and config.external_term == false then + config.R_args = { "--no-save" } + else + config.R_args = { "--sdi", "--no-save" } + end + end + wtime = (uv.hrtime() - wtime) / 1000000000 + require("r.edit").add_to_debug_info("windows setup", wtime, "Time") +end + +return M diff --git a/lua/r/rmd.lua b/lua/r/rmd.lua index 60bda499..38ad733f 100644 --- a/lua/r/rmd.lua +++ b/lua/r/rmd.lua @@ -1,8 +1,8 @@ -local inform = require("r").inform +local inform = require("r.log").inform local config = require("r.config").get_config() local send = require("r.send") local get_lang = require("r.utils").get_lang -local uv = vim.loop +local uv=vim.uv local M = {} diff --git a/lua/r/rnw.lua b/lua/r/rnw.lua index a7786219..ef0ce8bf 100644 --- a/lua/r/rnw.lua +++ b/lua/r/rnw.lua @@ -1,5 +1,5 @@ -local warn = require("r").warn -local inform = require("r").inform +local warn = require("r.log").warn +local inform = require("r.log").inform local send = require("r.send") local utils = require("r.utils") local get_lang = require("r.utils").get_lang diff --git a/lua/r/roxygen.lua b/lua/r/roxygen.lua index dd587f0e..0165ea15 100644 --- a/lua/r/roxygen.lua +++ b/lua/r/roxygen.lua @@ -1,4 +1,4 @@ -local warn = require("r").warn +local warn = require("r.log").warn local M = {} local parsers = require("nvim-treesitter.parsers") diff --git a/lua/r/rproj.lua b/lua/r/rproj.lua index fc2352d2..62af7852 100644 --- a/lua/r/rproj.lua +++ b/lua/r/rproj.lua @@ -1,5 +1,5 @@ local read_dcf = require("r.utils").read_dcf -local warn = require("r.init").warn +local warn = require("r.log").warn local M = {} diff --git a/lua/r/rstudio.lua b/lua/r/rstudio.lua index af68a625..3949a13d 100644 --- a/lua/r/rstudio.lua +++ b/lua/r/rstudio.lua @@ -1,5 +1,5 @@ local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn local M = {} M.start_RStudio = function() diff --git a/lua/r/run.lua b/lua/r/run.lua index 9c3ca924..9adf814d 100644 --- a/lua/r/run.lua +++ b/lua/r/run.lua @@ -2,15 +2,16 @@ local M = {} local config = require("r.config").get_config() local job = require("r.job") local edit = require("r.edit") -local warn = require("r").warn +local warn = require("r.log").warn local utils = require("r.utils") local send = require("r.send") local cursor = require("r.cursor") +local hooks = require("r.hooks") local what_R = "R" local R_pid = 0 local r_args local nseconds -local uv = vim.loop +local uv = vim.uv local start_R2 start_R2 = function() @@ -328,9 +329,7 @@ M.set_nvimcom_info = function(nvimcomversion, rpid, wid, r_info) end vim.g.R_Nvim_status = 7 - if config.hook.after_R_start then - vim.schedule(function() config.hook.after_R_start() end) - end + hooks.run(config, "after_R_start") send.set_send_cmd_fun() end diff --git a/lua/r/send.lua b/lua/r/send.lua index 37036aed..e4a3a643 100644 --- a/lua/r/send.lua +++ b/lua/r/send.lua @@ -1,6 +1,6 @@ local config = require("r.config").get_config() -local warn = require("r").warn -local inform = require("r").inform +local warn = require("r.log").warn +local inform = require("r.log").inform local utils = require("r.utils") local get_lang = require("r.utils").get_lang local edit = require("r.edit") diff --git a/lua/r/server.lua b/lua/r/server.lua index 3c20cf0d..f93fd3b3 100644 --- a/lua/r/server.lua +++ b/lua/r/server.lua @@ -1,9 +1,8 @@ local edit = require("r.edit") -local utils = require("r.utils") local job = require("r.job") local config = require("r.config").get_config() -local warn = require("r").warn -local uv = vim.loop +local warn = require("r.log").warn +local uv = vim.uv local b_warn = {} local b_err = {} local b_out = {} @@ -12,6 +11,7 @@ local o_err = {} local pkgbuild_attempt = false local rhelp_list = {} local building_objls = false +local check_executable = require("r.utils").check_executable local M = {} @@ -199,6 +199,28 @@ local start_rnvimserver = function() ) end +-- Function to build the package +local function build_package() + -- Change directory to config.tmpdir + local cmd = "cd " + .. vim.fn.shellescape(config.tmpdir) + .. " && " + .. vim.fn.shellescape("R") + .. " CMD build " + .. vim.fn.shellescape(config.rnvim_home .. "/nvimcom") + + uv.spawn("sh", { + args = { "-c", cmd }, + stdio = nil, + }, function(code) + if code == 0 then + M.check_nvimcom_version() + else + warn("Failed to build the package.") + end + end) +end + -- Check if the exit code of the script that built nvimcom was zero -- and if the file nvimcom_info seems to be OK (has three lines). local init_exit = function(_, data, _) @@ -215,21 +237,13 @@ local init_exit = function(_, data, _) -- R.nvim/nvimcom directory not found. Perhaps R running in remote machine... -- Try to use local R to build the nvimcom package. pkgbuild_attempt = true - if vim.fn.executable("R") == 1 then - local shf = { - "cd " .. config.tmpdir, - "R CMD build " .. config.rnvim_home .. "/nvimcom", - } - vim.fn.writefile(shf, config.tmpdir .. "/buildpkg.sh") - local obj = utils - .system({ "sh", config.tmpdir .. "/buildpkg.sh" }, { text = true }) - :wait() - if obj.code == 0 then - M.check_nvimcom_version() - cnv_again = 1 + check_executable("R", function(exists) + if exists then + build_package() + else + warn('"R" executable not found.') end - vim.fn.delete(config.tmpdir .. "/buildpkg.sh") - end + end) else if vim.fn.filereadable(vim.fn.expand("~/.R/Makevars")) == 1 then warn( diff --git a/lua/r/term.lua b/lua/r/term.lua index 7d3b0041..52d9ea03 100644 --- a/lua/r/term.lua +++ b/lua/r/term.lua @@ -1,7 +1,7 @@ local M = {} local config = require("r.config").get_config() -local warn = require("r").warn +local warn = require("r.log").warn local r_width = 80 local number_col local r_bufnr = nil diff --git a/lua/r/utils.lua b/lua/r/utils.lua index 583aa5d1..8adfd2b7 100644 --- a/lua/r/utils.lua +++ b/lua/r/utils.lua @@ -1,7 +1,30 @@ -local warn = require("r").warn +local warn = require("r.log").warn +local uv = vim.uv local M = {} +--- Tries to asynchronously run cmd with --version +--- callback function recieves true if exit code 0, otherwise false +--- false means: +--- 1. executable not found. +--- 2. Not enough memory to spawn a process. +--- 3. User does not have necessary file permissions. +--- 4. executable does not support --version flag. +---@param cmd string +---@param callback function +function M.check_executable(cmd, callback) + uv.spawn(cmd, { + args = { "--version" }, -- Assuming the executable supports '--version' + stdio = nil, -- We don't need to capture output here + }, function(code) + if code == 0 then + callback(true) + else + callback(false) + end + end) +end + --- Get language at current cursor position of rhelp buffer ---@return string local get_rhelp_lang = function() @@ -44,6 +67,12 @@ local get_rnw_lang = function() end end +function M.resolve_fullpaths(tbl) + for i, v in ipairs(tbl) do + tbl[i] = uv.fs_realpath(v) + end +end + --- Get language at current cursor position ---@return string function M.get_lang() @@ -244,8 +273,8 @@ function M.system(cmd, opts) end --- init state - local stdout = assert(vim.loop.new_pipe(false)) - local stderr = assert(vim.loop.new_pipe(false)) + local stdout = assert(uv.new_pipe(false)) + local stderr = assert(uv.new_pipe(false)) local stdout_data, stderr_data local state = { handle = nil, @@ -263,7 +292,7 @@ function M.system(cmd, opts) } --- run the command - state.handle, state.pid = vim.loop.spawn(cmd[1], { + state.handle, state.pid = uv.spawn(cmd[1], { args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, cwd = opts.cwd, diff --git a/lua/r/windows.lua b/lua/r/windows.lua index 11337497..44b5315d 100644 --- a/lua/r/windows.lua +++ b/lua/r/windows.lua @@ -1,6 +1,6 @@ local config = require("r.config").get_config() local utils = require("r.utils") -local warn = require("r").warn +local warn = require("r.log").warn local saved_home = nil local M = {}