From a656ba5361ed909ccf485f77ab195beb2092c1c4 Mon Sep 17 00:00:00 2001 From: Andrew Kwon Date: Sun, 3 Jan 2021 01:08:17 -0800 Subject: [PATCH] Initial commit --- .gitignore | 1 + .luacheckrc | 3 + lua/log.lua | 48 +++++++++ lua/presence/discord.lua | 197 +++++++++++++++++++++++++++++++++ lua/presence/files.lua | 50 +++++++++ lua/presence/init.lua | 227 +++++++++++++++++++++++++++++++++++++++ lua/struct.lua | 179 ++++++++++++++++++++++++++++++ plugin/presence.vim | 15 +++ 8 files changed, 720 insertions(+) create mode 100644 .gitignore create mode 100644 .luacheckrc create mode 100644 lua/log.lua create mode 100644 lua/presence/discord.lua create mode 100644 lua/presence/files.lua create mode 100644 lua/presence/init.lua create mode 100644 lua/struct.lua create mode 100644 plugin/presence.vim diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..fe78394 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,3 @@ +globals = { + 'vim', +} diff --git a/lua/log.lua b/lua/log.lua new file mode 100644 index 0000000..7528a9a --- /dev/null +++ b/lua/log.lua @@ -0,0 +1,48 @@ +local Log = {} + +Log.codes = {} + +Log.levels = { + { "debug", "Comment" }, + { "info", "None" }, + { "warn", "WarningMsg" }, + { "error", "ErrorMsg" }, +} + +function Log.new(options) + options = options or {} + + local logger = vim.deepcopy(Log) + logger.options = options + logger.options.level = options.level + + return logger +end + +setmetatable(Log, { + __call = function(_, ...) + return Log.new(...) + end, +}) + +-- Initialize logger with log functions for each level +for i = 1, #Log.levels do + local level, hl = unpack(Log.levels[i]) + + Log.codes[level] = i + + Log[level] = function(self, message) + -- Skip if log level is not set or the log is below the configured or default level + if not self.options.level or self.codes[level] < self.codes[self.options.level] then + return + end + + vim.schedule(function() + vim.cmd(string.format("echohl %s", hl)) + vim.cmd(string.format([[echom "[%s] %s"]], "presence.nvim", vim.fn.escape(message, '"'))) + vim.cmd("echohl NONE") + end) + end +end + +return Log diff --git a/lua/presence/discord.lua b/lua/presence/discord.lua new file mode 100644 index 0000000..3ce81ec --- /dev/null +++ b/lua/presence/discord.lua @@ -0,0 +1,197 @@ +local Discord = {} + +Discord.opcodes = { + auth = 0, + frame = 1, + closed = 2, +} + +-- Discord RPC Subscription events +-- https://discord.com/developers/docs/topics/rpc#commands-and-events-rpc-events +-- Ready: https://discord.com/developers/docs/topics/rpc#ready +-- Error: https://discord.com/developers/docs/topics/rpc#error +Discord.events = { + READY = "READY", + ERROR = "ERROR", +} + +local struct = require("struct") + +-- Initialize a new Discord RPC client +function Discord:new(options) + self.log = options.logger + self.client_id = options.client_id + + self.ipc_path = self.find_ipc_path() + self.pipe = vim.loop.new_pipe(true) + + return self +end + +-- Connect to the local Discord RPC socket +-- TODO Might need to check for pipes ranging from discord-ipc-0 to discord-ipc-9: +-- https://github.com/discord/discord-rpc/blob/master/documentation/hard-mode.md#notes +function Discord:connect(on_connect) + if self.pipe:is_closing() then + self.pipe = vim.loop.new_pipe(true) + end + + self.pipe:connect(self.ipc_path.."/discord-ipc-0", on_connect) +end + +function Discord:is_connected() + return self.pipe:is_active() +end + +-- Disconnect from the local Discord RPC socket +function Discord:disconnect(on_close) + self.pipe:shutdown() + if not self.pipe:is_closing() then + self.pipe:close(on_close) + end +end + +-- Make a remote procedure call to Discord +-- Callback argument in format: on_response(error[, response_table]) +function Discord:call(opcode, payload, on_response) + self.encode_json(payload, function(body) + -- Start reading for the response + self.pipe:read_start(function(...) + self:read_message(payload.nonce, on_response, ...) + end) + + -- Construct message denoting little endian, auth opcode, msg length + local message = struct.pack("' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local val = tonumber(table.remove(vars, 1)) + + local bytes = {} + for _ = 1, n do + table.insert(bytes, string.char(val % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt:find('[fd]') then + local val = tonumber(table.remove(vars, 1)) + local sign = 0 + + if val < 0 then + sign = 1 + val = -val + end + + local mantissa, exponent = math.frexp(val) + if val == 0 then + mantissa = 0 + exponent = 0 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24) + exponent = exponent + ((opt == 'd') and 1022 or 126) + end + + local bytes = {} + if opt == 'd' then + val = mantissa + for _ = 1, 6 do + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + else + table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8))) + val = math.floor(mantissa / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8))) + val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8))) + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt == 's' then + table.insert(stream, tostring(table.remove(vars, 1))) + table.insert(stream, string.char(0)) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + local str = tostring(table.remove(vars, 1)) + local len = tonumber(n) + if len <= 0 then + len = str:len() + end + if len - str:len() > 0 then + str = str .. string.rep(' ', len - str:len()) + end + table.insert(stream, str:sub(1, len)) + end + end + + return table.concat(stream) +end + +function struct.unpack(format, stream, pos) + local vars = {} + local iterator = pos or 1 + local endianness = true + + for i = 1, format:len() do + local opt = format:sub(i, i) + + if opt == '<' then + endianness = true + elseif opt == '>' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local signed = opt:lower() == opt + + local val = 0 + for j = 1, n do + local byte = string.byte(stream:sub(iterator, iterator)) + if endianness then + val = val + byte * (2 ^ ((j - 1) * 8)) + else + val = val + byte * (2 ^ ((n - j) * 8)) + end + iterator = iterator + 1 + end + + if signed and val >= 2 ^ (n * 8 - 1) then + val = val - 2 ^ (n * 8) + end + + table.insert(vars, math.floor(val)) + elseif opt:find('[fd]') then + local n = (opt == 'd') and 8 or 4 + local x = stream:sub(iterator, iterator + n - 1) + iterator = iterator + n + + if not endianness then + x = string.reverse(x) + end + + local sign = 1 + local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128) + for j = n - 2, 1, -1 do + mantissa = mantissa * (2 ^ 8) + string.byte(x, j) + end + + if string.byte(x, n) > 127 then + sign = -1 + end + + local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + + math.floor(string.byte(x, n - 1) / + ((opt == 'd') and 16 or 128)) + if exponent == 0 then + table.insert(vars, 0.0) + else + mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign + table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127))) + end + elseif opt == 's' then + local bytes = {} + for j = iterator, stream:len() do + if stream:sub(j,j) == string.char(0) or stream:sub(j) == '' then + break + end + + table.insert(bytes, stream:sub(j, j)) + end + + local str = table.concat(bytes) + iterator = iterator + str:len() + 1 + table.insert(vars, str) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + local len = tonumber(n) + if len <= 0 then + len = table.remove(vars) + end + + table.insert(vars, stream:sub(iterator, iterator + len - 1)) + iterator = iterator + len + end + end + + return unpack(vars) +end + +return struct diff --git a/plugin/presence.vim b/plugin/presence.vim new file mode 100644 index 0000000..7892b23 --- /dev/null +++ b/plugin/presence.vim @@ -0,0 +1,15 @@ +" Define autocommands to handle auto-update events +augroup presence_events + autocmd! + if g:presence_auto_update + autocmd BufRead * lua package.loaded.presence:update() + endif +augroup END + +" Fallback to setting up the plugin automatically +if !exists("g:presence_has_setup") +lua << EOF + local Presence = require("presence"):setup() + Presence.log:debug("Custom setup not detected, plugin set up using defaults") +EOF +endif