1
0
Fork 0
mirror of https://github.com/jiriks74/presence.nvim synced 2025-06-21 20:18:57 +02:00

Add WSL 1 support

* Update README & remove unnecessary relay script
* Remove awk usage with explicit output parsers
* Ensure Presence is canceled on leave event
* Use release information to determine WSL 1 use
* Support P2P state sharing in WSL with ss
    * Use ss if netstat is not available
    * Use jobstart instead of io.popen to get WSL nvim sockets
* Detect OS via uname
* Add support for WSL
This commit is contained in:
Andrew Kwon 2021-07-09 13:14:38 -07:00
parent 774994a5b9
commit 080d24394b
2 changed files with 191 additions and 68 deletions
lua/presence

View file

@ -60,21 +60,13 @@ Presence.socket = vim.v.servername
Presence.workspace = nil
Presence.workspaces = {}
-- Get the operating system name (eh should be good enough)
-- http://www.lua.org/manual/5.3/manual.html#pdf-package.config
local separator = package.config:sub(1,1)
Presence.os = {
name = separator == [[\]] and "windows" or "unix",
path_separator = separator,
}
local log = require("lib.log")
local msgpack = require("deps.msgpack")
local serpent = require("deps.serpent")
local Discord = require("presence.discord")
local file_assets = require("presence.file_assets")
local file_explorers = require("presence.file_explorers")
local plugin_managers = require("presence.plugin_managers")
local Discord = require("presence.discord")
function Presence:setup(options)
options = options or {}
@ -83,7 +75,30 @@ function Presence:setup(options)
-- Initialize logger
self:set_option("log_level", nil, false)
self.log = log:init({ level = options.log_level })
self.log:debug("Setting up plugin...")
-- Get operating system information including path separator
-- http://www.lua.org/manual/5.3/manual.html#pdf-package.config
local uname = vim.loop.os_uname()
local separator = package.config:sub(1,1)
local wsl_distro_name = os.getenv("WSL_DISTRO_NAME")
local os_name = self.get_os_name(uname)
self.os = {
name = os_name,
is_wsl = uname.release:find("Microsoft") ~= nil,
path_separator = separator,
}
-- Print setup message with OS information
local setup_message_fmt = "Setting up plugin for %s"
if self.os.name then
local setup_message = self.os.is_wsl
and string.format(setup_message_fmt.." in WSL (%s)", self.os.name, vim.inspect(wsl_distro_name))
or string.format(setup_message_fmt, self.os.name)
self.log:debug(setup_message)
else
self.log:error(string.format("Unable to detect operating system: %s"))
self.log:debug(vim.inspect(vim.loop.os_uname()))
end
-- Use the default or user-defined client id if provided
if options.client_id then
@ -106,19 +121,23 @@ function Presence:setup(options)
self:set_option("workspace_text", "Working on %s")
self:set_option("line_number_text", "Line %s out of %s")
local discord_socket = self:get_discord_socket()
if not discord_socket then
self.log:error("Failed to get Discord IPC socket")
-- Get and check discord socket path
local discord_socket_path = self:get_discord_socket_path()
if discord_socket_path then
self.log:debug(string.format("Using Discord IPC socket path: %s", discord_socket_path))
self:check_discord_socket(discord_socket_path)
else
self.log:error("Failed to determine Discord IPC socket path")
end
-- Initialize discord RPC client
self.discord = Discord:init({
logger = self.log,
client_id = options.client_id,
ipc_socket = discord_socket,
ipc_socket = discord_socket_path,
})
-- Seed instance id using unique socket address
-- Seed instance id using unique socket path
local seed_nums = {}
self.socket:gsub(".", function(c) table.insert(seed_nums, c:byte()) end)
self.id = self.discord.generate_uuid(tonumber(table.concat(seed_nums)) / os.clock())
@ -141,6 +160,19 @@ function Presence:setup(options)
return self
end
-- Normalize the OS name from uname
function Presence.get_os_name(uname)
if uname.sysname:find("Windows") then
return "windows"
elseif uname.sysname:find("Darwin") then
return "macos"
elseif uname.sysname:find("Linux") then
return "linux"
end
return "unknown"
end
-- To ensure consistent option values, coalesce true and false values to 1 and 0
function Presence.coalesce_option(value)
if type(value) == "boolean" then
@ -181,6 +213,28 @@ function Presence:check_dup_options(option)
end
end
-- Check the Discord socket at the given path
function Presence:check_discord_socket(path)
self.log:debug(string.format("Checking Discord IPC socket at %s...", path))
-- Asynchronously check socket path via stat
vim.loop.fs_stat(path, function(err, stats)
if err then
local err_msg = "Failed to get socket information"
self.log:error(string.format("%s: %s", err_msg, err))
return
end
if stats.type ~= "socket" then
local warning_msg = "Found unexpected Discord IPC socket type"
self.log:warn(string.format("%s: %s", warning_msg, err))
return
end
self.log:debug(string.format("Checked Discord IPC socket, looks good!"))
end)
end
-- Send a nil activity to unset the presence
function Presence:cancel()
self.log:debug("Canceling Discord presence...")
@ -289,31 +343,44 @@ function Presence:authorize(on_done)
end)
end
-- Find the the IPC path in temp runtime directories
function Presence:get_discord_socket()
-- Find the Discord socket from temp runtime directories
function Presence:get_discord_socket_path()
local sock_name = "discord-ipc-0"
local sock_path = nil
if self.os.name == "windows" then
return [[\\.\pipe\]]..sock_name
end
if self.os.is_wsl then
-- Use socket created by relay for WSL
sock_path = "/var/run/"..sock_name
elseif self.os.name == "windows" then
-- Use named pipe in NPFS for Windows
sock_path = [[\\.\pipe\]]..sock_name
elseif self.os.name == "macos" then
-- Use $TMPDIR for macOS
local path = os.getenv("TMPDIR")
sock_path = path:match("/$")
and path..sock_name
or path.."/"..sock_name
elseif self.os.name == "linux" then
-- Check various temp directory environment variables
local env_vars = {
"XDG_RUNTIME_DIR",
"TEMP",
"TMP",
"TMPDIR",
}
local env_vars = {
"XDG_RUNTIME_DIR",
"TEMP",
"TMP",
"TMPDIR",
}
for i = 1, #env_vars do
local var = env_vars[i]
local path = vim.loop.os_getenv(var)
if path then
self.log:debug(string.format("Using runtime path: %s", path))
return path:match("/$") and path..sock_name or path.."/"..sock_name
for i = 1, #env_vars do
local var = env_vars[i]
local path = os.getenv(var)
if path then
self.log:debug(string.format("Using runtime path: %s", path))
sock_path = path:match("/$") and path..sock_name or path.."/"..sock_name
break
end
end
end
return nil
return sock_path
end
-- Gets the file path of the current vim buffer
@ -399,34 +466,85 @@ function Presence:get_status_text(filename)
end
end
-- Get all active local nvim unix domain socket addresses
function Presence:get_nvim_socket_addrs(on_done)
self.log:debug("Getting nvim socket addresses...")
-- Get all local nvim socket paths
function Presence:get_nvim_socket_paths(on_done)
self.log:debug("Getting nvim socket paths...")
local sockets = {}
local parser = {}
local cmd
-- TODO: Find a better way to get paths of remote Neovim sockets lol
local commands = {
unix = table.concat({
"netstat -u",
[[grep --color=never "nvim.*/0"]],
[[awk -F "[ :]+" '{print $9}']],
"sort",
"uniq",
}, "|"),
windows = {
if self.os.is_wsl then
-- TODO: There needs to be a better way of doing this... no support for ss/netstat?
-- (See https://github.com/microsoft/WSL/issues/2249)
local cmd_fmt = "for file in %s/nvim*; do echo $file/0; done"
local shell_cmd = string.format(cmd_fmt, vim.loop.os_tmpdir() or "/tmp")
cmd = {
"sh",
"-c",
shell_cmd,
}
elseif self.os.name == "windows" then
cmd = {
"powershell.exe",
"-Command",
[[(Get-ChildItem \\.\pipe\).FullName | findstr 'nvim']],
},
}
local cmd = commands[self.os.name]
}
elseif self.os.name == "macos" then
if vim.fn.executable("netstat") == 0 then
self.log:warn("Unable to get nvim socket paths: `netstat` command unavailable")
return
end
-- Define macOS BSD netstat output parser
function parser.parse(data)
return data:match("%s(/.+)")
end
cmd = table.concat({
"netstat -u",
[[grep --color=never "nvim.*/0"]],
}, "|")
elseif self.os.name == "linux" then
if vim.fn.executable("netstat") == 1 then
-- Use `netstat` if available
cmd = table.concat({
"netstat -u",
[[grep --color=never "nvim.*/0"]],
}, "|")
-- Define netstat output parser
function parser.parse(data)
return data:match("%s(/.+)")
end
elseif vim.fn.executable("ss") == 1 then
-- Use `ss` if available
cmd = table.concat({
"ss -lx",
[[grep "nvim.*/0"]],
}, "|")
-- Define ss output parser
function parser.parse(data)
return data:match("%s(/.-)%s")
end
else
local warning_msg = "Unable to get nvim socket paths: `netstat` and `ss` commands unavailable"
self.log:warn(warning_msg)
return
end
else
local warning_fmt = "Unable to get nvim socket paths: Unexpected OS: %s"
self.log:warn(string.format(warning_fmt, self.os.name))
return
end
local sockets = {}
local function handle_data(_, data)
if not data then return end
for i = 1, #data do
local socket = vim.trim(data[i])
if socket ~= "" and socket ~= self.socket then
local socket = parser.parse and parser.parse(vim.trim(data[i])) or vim.trim(data[i])
if socket and socket ~= "" and socket ~= self.socket then
table.insert(sockets, socket)
end
end
@ -436,15 +554,17 @@ function Presence:get_nvim_socket_addrs(on_done)
if not data then return end
if data[1] ~= "" then
self.log:error(data[1])
self.log:error(string.format("Unable to get nvim socket paths: %s", data[1]))
end
end
local function handle_exit()
self.log:debug(string.format("Got nvim socket addresses: %s", vim.inspect(sockets)))
self.log:debug(string.format("Got nvim socket paths: %s", vim.inspect(sockets)))
on_done(sockets)
end
local cmd_str = type(cmd) == "table" and table.concat(cmd, ", ") or cmd
self.log:debug(string.format("Executing command: `%s`", cmd_str))
vim.fn.jobstart(cmd, {
on_stdout = handle_data,
on_stderr = handle_error,
@ -766,9 +886,9 @@ function Presence:register_and_sync_peer(id, socket)
end
-- Register self to any remote Neovim instances
-- Simply emits to all nvim socket addresses as we have not yet been synced with peer list
-- Simply emits to all nvim sockets as we have not yet been synced with peer list
function Presence:register_self()
self:get_nvim_socket_addrs(function(sockets)
self:get_nvim_socket_paths(function(sockets)
if #sockets == 0 then
self.log:debug("No other remote nvim instances")
return
@ -895,6 +1015,7 @@ end
function Presence:handle_vim_leave_pre()
self.log:debug("Handling VimLeavePre event...")
self:unregister_self()
self:cancel()
end
-- WinEnter events force-update the current buffer presence unless it's a quickfix window
@ -947,7 +1068,7 @@ function Presence:handle_buf_enter()
self:update()
end
-- WinLeave events cancel the current buffer presence
-- BufAdd events force-update the presence for the current buffer unless it's a quickfix window
function Presence:handle_buf_add()
self.log:debug("Handling BufAdd event...")