diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ae98a5a..580930c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,47 +1,98 @@ name: Issue report description: Report any errors, bugs, or unexpected behaviors related to presence.nvim +title: "[Bug]: " labels: [bug] +assignees: + - jiriks74 body: - type: markdown attributes: value: | Before reporting, please search [existing issues](https://github.com/andweeb/presence.nvim/issues) and make sure that presence.nvim is updated to the latest version. + + - type: checkboxes + attributes: + label: Are you on the latest version? + options: + - label: I have updated to the latest version. + required: true + + - type: checkboxes + attributes: + label: Have you tried it with default config? + options: + - label: I have tried the default config. + required: true + - type: textarea attributes: label: "Description" description: "A short summary of the error, bug, or unexpected behavior you're facing." validations: required: true + - type: textarea attributes: label: "Neovim version" description: "Output of `nvim --version`" render: markdown placeholder: | - NVIM v0.6.0-dev+209-g0603eba6e + NVIM: v0.6.0-dev+209-g0603eba6e Build type: Release - LuaJIT 2.1.0-beta3 + LuaJIT: 2.1.0-beta3 + value: | + NVIM: + Build type: + LuaJIT: validations: required: true + - type: input attributes: label: "OS information" placeholder: "macOS 12.0.1" validations: required: true + - type: textarea attributes: label: "Steps to reproduce" - description: "Steps to reproduce the issue with your config(s) if applicable" + description: "Steps to reproduce the issue with your config(s) if applicable." placeholder: | 1. Setup presence.nvim with `require("presence"):setup({...})` 2. Run Neovim with `nvim test.txt` 3. ... validations: required: true + - type: textarea attributes: label: "Logs" - description: "The full list of `:messages` from one or more `nvim` instances" + description: "The full list of `:messages` from one or more `nvim` instances.\nPlease insert the logs into code blocks." + placeholder: | + <details> + + ``` + [presence.nvim] Using runtime path: /run/user/1000 + [presence.nvim] Using Discord IPC socket path: /run/user/1000/discord-ipc-0 + [presence.nvim] Checking Discord IPC socket at /run/user/1000/discord-ipc-0... + ``` + + </details> + value: | + <details> + + ``` + + ``` + + </details> validations: required: true + + - type: textarea + attributes: + label: "Aditional info" + description: "If you'd like to add anything else put it here." + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 11fc491..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..9ebd200 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Report any errors, bugs, or unexpected behaviors related to presence.nvim +title: "[FEAT]: " +labels: [enhancement] +assignees: + - jiriks74 +body: + - type: textarea + attributes: + label: Is your feature request related to a problem? + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when... + validations: + required: true + + - type: textarea + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: false + + - type: textarea + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 571609c..87145d9 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -1,7 +1,7 @@ name: Luacheck on: [push, pull_request] jobs: - sile: + Luacheck: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/stylua.yml b/.github/workflows/stylua.yml new file mode 100644 index 0000000..639c7e1 --- /dev/null +++ b/.github/workflows/stylua.yml @@ -0,0 +1,15 @@ +name: StyLua +on: [push, pull_request] +jobs: + StyLuacheck: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: StyLua check + uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GH_TOKEN }} + version: v0.18.2 # NOTE: we recommend pinning to a specific version in case of formatting changes + # CLI arguments + args: --check . diff --git a/README.md b/README.md index 2744b6c..29e3119 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ require("presence").setup({ auto_update = true, -- Update activity based on autocmd events (if `false`, map or manually execute `:lua package.loaded.presence:update()`) neovim_image_text = "The One True Text Editor", -- Text displayed when hovered over the Neovim image main_image = "neovim", -- Main image display (either "neovim" or "file") - client_id = "793271441293967371", -- Use your own Discord application client id (not recommended) + client_id = "1172122807501594644", -- Use your own Discord application client id (not recommended) log_level = nil, -- Log messages at or above this level (one of the following: "debug", "info", "warn", "error") debounce_timeout = 10, -- Number of seconds to debounce events (or calls to `:lua package.loaded.presence:update(<filename>, true)`) enable_line_number = false, -- Displays the current line number instead of the current project @@ -91,7 +91,7 @@ Or if global variables are more your thing, you can use any of the following ins let g:presence_auto_update = 1 let g:presence_neovim_image_text = "The One True Text Editor" let g:presence_main_image = "neovim" -let g:presence_client_id = "793271441293967371" +let g:presence_client_id = "1172122807501594644" let g:presence_log_level let g:presence_debounce_timeout = 10 let g:presence_enable_line_number = 0 @@ -142,6 +142,8 @@ errors: `luacheck lua` **Please use [Conventional Commits](https://www.conventionalcommits.org/) if you want to contribute. It makes everyones jobs easier.** +**This project uses [StyLua](https://github.com/JohnnyMorganz/StyLua). Please format your code using StyLua for better readability** + Pull requests are very welcome, feel free to open an issue to work on or message [me (@jiriks74)](https://discordapp.com/users/517810049360461837) on my [Discord server](https://discord.gg/cCq3qcB4jB)! diff --git a/lua/deps/msgpack.lua b/lua/deps/msgpack.lua index a4b928b..d7ccfdd 100644 --- a/lua/deps/msgpack.lua +++ b/lua/deps/msgpack.lua @@ -9,120 +9,140 @@ local double_encode_count = 0 -- cache bitops local band, rshift = luabit.band, luabit.brshift if not rshift then -- luajit differ from luabit - rshift = luabit.rshift + rshift = luabit.rshift end -local function byte_mod(x,v) - if x < 0 then - x = x + 256 - end - return (x%v) +local function byte_mod(x, v) + if x < 0 then + x = x + 256 + end + return (x % v) end - -- buffer local strbuf = "" -- for unpacking local strary = {} -- for packing -local function strary_append_int16(n,h) - if n < 0 then - n = n + 65536 - end - table.insert( strary, tostr(h, math.floor(n / 256), n % 256 ) ) +local function strary_append_int16(n, h) + if n < 0 then + n = n + 65536 + end + table.insert(strary, tostr(h, math.floor(n / 256), n % 256)) end -local function strary_append_int32(n,h) - if n < 0 then - n = n + 4294967296 - end - table.insert(strary, tostr(h, - math.floor(n / 16777216), - math.floor(n / 65536) % 256, - math.floor(n / 256) % 256, - n % 256 )) +local function strary_append_int32(n, h) + if n < 0 then + n = n + 4294967296 + end + table.insert( + strary, + tostr(h, math.floor(n / 16777216), math.floor(n / 65536) % 256, math.floor(n / 256) % 256, n % 256) + ) end local doubleto8bytes local strary_append_double = function(n) - -- assume double - double_encode_count = double_encode_count + 1 - local b = doubleto8bytes(n) - table.insert( strary, tostr(0xcb)) - table.insert( strary, string.reverse(b) ) -- reverse: make big endian double precision + -- assume double + double_encode_count = double_encode_count + 1 + local b = doubleto8bytes(n) + table.insert(strary, tostr(0xcb)) + table.insert(strary, string.reverse(b)) -- reverse: make big endian double precision end --- IEEE 754 -- out little endian doubleto8bytes = function(x) - local function grab_byte(v) - return math.floor(v / 256), tostr(math.fmod(math.floor(v), 256)) - end - local sign = 0 - if x < 0 then sign = 1; x = -x end - local mantissa, exponent = math.frexp(x) - if x == 0 then -- zero - mantissa, exponent = 0, 0 - elseif x == 1/0 then - mantissa, exponent = 0, 2047 - else - mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) - exponent = exponent + 1022 - end + local function grab_byte(v) + return math.floor(v / 256), tostr(math.fmod(math.floor(v), 256)) + end + local sign = 0 + if x < 0 then + sign = 1 + x = -x + end + local mantissa, exponent = math.frexp(x) + if x == 0 then -- zero + mantissa, exponent = 0, 0 + elseif x == 1 / 0 then + mantissa, exponent = 0, 2047 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53) + exponent = exponent + 1022 + end - local v, byte = "" -- convert to bytes - x = mantissa - for _ = 1,6 do - _, byte = grab_byte(x); v = v..byte -- 47:0 - end - x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48 - x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56 - return v, x + local v, byte = "" -- convert to bytes + x = mantissa + for _ = 1, 6 do + _, byte = grab_byte(x) + v = v .. byte -- 47:0 + end + x, byte = grab_byte(exponent * 16 + x) + v = v .. byte -- 55:48 + x, byte = grab_byte(sign * 128 + x) + v = v .. byte -- 63:56 + return v, x end local function bitstofrac(ary) - local x = 0 - local cur = 0.5 - for _, v in ipairs(ary) do - x = x + cur * v - cur = cur / 2 - end - return x + local x = 0 + local cur = 0.5 + for _, v in ipairs(ary) do + x = x + cur * v + cur = cur / 2 + end + return x end local function bytestobits(ary) - local out={} - for _, v in ipairs(ary) do - for j = 0, 7, 1 do - table.insert(out, band( rshift(v,7-j), 1 ) ) - end - end - return out + local out = {} + for _, v in ipairs(ary) do + for j = 0, 7, 1 do + table.insert(out, band(rshift(v, 7 - j), 1)) + end + end + return out end -- get little endian local function bytestodouble(v) - -- sign:1bit - -- exp: 11bit (2048, bias=1023) - local sign = math.floor(v:byte(8) / 128) - local exp = band( v:byte(8), 127 ) * 16 + rshift( v:byte(7), 4 ) - 1023 -- bias - -- frac: 52 bit - local fracbytes = { - band( v:byte(7), 15 ), v:byte(6), v:byte(5), v:byte(4), v:byte(3), v:byte(2), v:byte(1) -- big endian - } - local bits = bytestobits(fracbytes) + -- sign:1bit + -- exp: 11bit (2048, bias=1023) + local sign = math.floor(v:byte(8) / 128) + local exp = band(v:byte(8), 127) * 16 + rshift(v:byte(7), 4) - 1023 -- bias + -- frac: 52 bit + local fracbytes = { + band(v:byte(7), 15), + v:byte(6), + v:byte(5), + v:byte(4), + v:byte(3), + v:byte(2), + v:byte(1), -- big endian + } + local bits = bytestobits(fracbytes) - for _ = 1, 4 do table.remove(bits,1) end + for _ = 1, 4 do + table.remove(bits, 1) + end - if sign == 1 then sign = -1 else sign = 1 end + if sign == 1 then + sign = -1 + else + sign = 1 + end - local frac = bitstofrac(bits) - if exp == -1023 and frac==0 then return 0 end - if exp == 1024 and frac==0 then return 1/0 *sign end + local frac = bitstofrac(bits) + if exp == -1023 and frac == 0 then + return 0 + end + if exp == 1024 and frac == 0 then + return 1 / 0 * sign + end - local real = math.ldexp(1+frac,exp) + local real = math.ldexp(1 + frac, exp) - return real * sign + return real * sign end --- packers @@ -130,265 +150,285 @@ end local packers = {} packers.dynamic = function(data) - local t = type(data) - return packers[t](data) + local t = type(data) + return packers[t](data) end packers["nil"] = function() - table.insert( strary, tostr(0xc0)) + table.insert(strary, tostr(0xc0)) end packers.boolean = function(data) - if data then -- pack true - table.insert( strary, tostr(0xc3)) - else -- pack false - table.insert( strary, tostr(0xc2)) - end + if data then -- pack true + table.insert(strary, tostr(0xc3)) + else -- pack false + table.insert(strary, tostr(0xc2)) + end end packers.number = function(n) - if math.floor(n) == n then -- integer - if n >= 0 then -- positive integer - if n < 128 then -- positive fixnum - table.insert( strary, tostr(n)) - elseif n < 256 then -- uint8 - table.insert(strary, tostr(0xcc,n)) - elseif n < 65536 then -- uint16 - strary_append_int16(n,0xcd) - elseif n < 4294967296 then -- uint32 - strary_append_int32(n,0xce) - else -- lua cannot handle uint64, so double - strary_append_double(n) - end - else -- negative integer - if n >= -32 then -- negative fixnum - table.insert( strary, tostr( 0xe0 + ((n+256)%32)) ) - elseif n >= -128 then -- int8 - table.insert( strary, tostr(0xd0,byte_mod(n,0x100))) - elseif n >= -32768 then -- int16 - strary_append_int16(n,0xd1) - elseif n >= -2147483648 then -- int32 - strary_append_int32(n,0xd2) - else -- lua cannot handle int64, so double - strary_append_double(n) - end - end - else -- floating point - strary_append_double(n) - end + if math.floor(n) == n then -- integer + if n >= 0 then -- positive integer + if n < 128 then -- positive fixnum + table.insert(strary, tostr(n)) + elseif n < 256 then -- uint8 + table.insert(strary, tostr(0xcc, n)) + elseif n < 65536 then -- uint16 + strary_append_int16(n, 0xcd) + elseif n < 4294967296 then -- uint32 + strary_append_int32(n, 0xce) + else -- lua cannot handle uint64, so double + strary_append_double(n) + end + else -- negative integer + if n >= -32 then -- negative fixnum + table.insert(strary, tostr(0xe0 + ((n + 256) % 32))) + elseif n >= -128 then -- int8 + table.insert(strary, tostr(0xd0, byte_mod(n, 0x100))) + elseif n >= -32768 then -- int16 + strary_append_int16(n, 0xd1) + elseif n >= -2147483648 then -- int32 + strary_append_int32(n, 0xd2) + else -- lua cannot handle int64, so double + strary_append_double(n) + end + end + else -- floating point + strary_append_double(n) + end end packers.string = function(data) - local n = #data - if n < 32 then - table.insert( strary, tostr( 0xa0+n ) ) - elseif n < 65536 then - strary_append_int16(n,0xda) - elseif n < 4294967296 then - strary_append_int32(n,0xdb) - else - error("overflow") - end - table.insert( strary, data) + local n = #data + if n < 32 then + table.insert(strary, tostr(0xa0 + n)) + elseif n < 65536 then + strary_append_int16(n, 0xda) + elseif n < 4294967296 then + strary_append_int32(n, 0xdb) + else + error("overflow") + end + table.insert(strary, data) end packers["function"] = function() - error("unimplemented:function") + error("unimplemented:function") end packers.userdata = function() - error("unimplemented:userdata") + error("unimplemented:userdata") end packers.thread = function() - error("unimplemented:thread") + error("unimplemented:thread") end packers.table = function(data) - local is_map,ndata,nmax = false,0,0 - for k,_ in pairs(data) do - if type(k) == "number" then - if k > nmax then nmax = k end - else is_map = true end - ndata = ndata+1 - end - if is_map then -- pack as map - if ndata < 16 then - table.insert( strary, tostr(0x80+ndata)) - elseif ndata < 65536 then - strary_append_int16(ndata,0xde) - elseif ndata < 4294967296 then - strary_append_int32(ndata,0xdf) - else - error("overflow") - end - for k,v in pairs(data) do - packers[type(k)](k) - packers[type(v)](v) - end - else -- pack as array - if nmax < 16 then - table.insert( strary, tostr( 0x90+nmax ) ) - elseif nmax < 65536 then - strary_append_int16(nmax,0xdc) - elseif nmax < 4294967296 then - strary_append_int32(nmax,0xdd) - else - error("overflow") - end - for i=1,nmax do packers[type(data[i])](data[i]) end - end + local is_map, ndata, nmax = false, 0, 0 + for k, _ in pairs(data) do + if type(k) == "number" then + if k > nmax then + nmax = k + end + else + is_map = true + end + ndata = ndata + 1 + end + if is_map then -- pack as map + if ndata < 16 then + table.insert(strary, tostr(0x80 + ndata)) + elseif ndata < 65536 then + strary_append_int16(ndata, 0xde) + elseif ndata < 4294967296 then + strary_append_int32(ndata, 0xdf) + else + error("overflow") + end + for k, v in pairs(data) do + packers[type(k)](k) + packers[type(v)](v) + end + else -- pack as array + if nmax < 16 then + table.insert(strary, tostr(0x90 + nmax)) + elseif nmax < 65536 then + strary_append_int16(nmax, 0xdc) + elseif nmax < 4294967296 then + strary_append_int32(nmax, 0xdd) + else + error("overflow") + end + for i = 1, nmax do + packers[type(data[i])](data[i]) + end + end end -- types decoding local types_map = { - [0xc0] = "nil", - [0xc2] = "false", - [0xc3] = "true", - [0xca] = "float", - [0xcb] = "double", - [0xcc] = "uint8", - [0xcd] = "uint16", - [0xce] = "uint32", - [0xcf] = "uint64", - [0xd0] = "int8", - [0xd1] = "int16", - [0xd2] = "int32", - [0xd3] = "int64", - [0xda] = "raw16", - [0xdb] = "raw32", - [0xdc] = "array16", - [0xdd] = "array32", - [0xde] = "map16", - [0xdf] = "map32", + [0xc0] = "nil", + [0xc2] = "false", + [0xc3] = "true", + [0xca] = "float", + [0xcb] = "double", + [0xcc] = "uint8", + [0xcd] = "uint16", + [0xce] = "uint32", + [0xcf] = "uint64", + [0xd0] = "int8", + [0xd1] = "int16", + [0xd2] = "int32", + [0xd3] = "int64", + [0xda] = "raw16", + [0xdb] = "raw32", + [0xdc] = "array16", + [0xdd] = "array32", + [0xde] = "map16", + [0xdf] = "map32", } local type_for = function(n) - - if types_map[n] then return types_map[n] - elseif n < 0xc0 then - if n < 0x80 then return "fixnum_posi" - elseif n < 0x90 then return "fixmap" - elseif n < 0xa0 then return "fixarray" - else return "fixraw" end - elseif n > 0xdf then return "fixnum_neg" - else return "undefined" end + if types_map[n] then + return types_map[n] + elseif n < 0xc0 then + if n < 0x80 then + return "fixnum_posi" + elseif n < 0x90 then + return "fixmap" + elseif n < 0xa0 then + return "fixarray" + else + return "fixraw" + end + elseif n > 0xdf then + return "fixnum_neg" + else + return "undefined" + end end local types_len_map = { - uint16 = 2, uint32 = 4, uint64 = 8, - int16 = 2, int32 = 4, int64 = 8, - float = 4, double = 8, + uint16 = 2, + uint32 = 4, + uint64 = 8, + int16 = 2, + int32 = 4, + int64 = 8, + float = 4, + double = 8, } - - - --- unpackers local unpackers = {} -local unpack_number = function(offset,ntype,nlen) - local b1,b2,b3,b4,b5,b6,b7,b8 - if nlen>=2 then - b1,b2 = string.byte( strbuf, offset+1, offset+2 ) - end - if nlen>=4 then - b3,b4 = string.byte( strbuf, offset+3, offset+4 ) - end - if nlen>=8 then - b5,b6,b7,b8 = string.byte( strbuf, offset+5, offset+8 ) - end +local unpack_number = function(offset, ntype, nlen) + local b1, b2, b3, b4, b5, b6, b7, b8 + if nlen >= 2 then + b1, b2 = string.byte(strbuf, offset + 1, offset + 2) + end + if nlen >= 4 then + b3, b4 = string.byte(strbuf, offset + 3, offset + 4) + end + if nlen >= 8 then + b5, b6, b7, b8 = string.byte(strbuf, offset + 5, offset + 8) + end - if ntype == "uint16_t" then - return b1 * 256 + b2 - elseif ntype == "uint32_t" then - return b1*65536*256 + b2*65536 + b3 * 256 + b4 - elseif ntype == "int16_t" then - local n = b1 * 256 + b2 - local nn = (65536 - n)*-1 - if nn == -65536 then nn = 0 end - return nn - elseif ntype == "int32_t" then - local n = b1*65536*256 + b2*65536 + b3 * 256 + b4 - local nn = ( 4294967296 - n ) * -1 - if nn == -4294967296 then nn = 0 end - return nn - elseif ntype == "double_t" then - local s = tostr(b8,b7,b6,b5,b4,b3,b2,b1) - double_decode_count = double_decode_count + 1 - local n = bytestodouble( s ) - return n - else - error("unpack_number: not impl:" .. ntype ) - end + if ntype == "uint16_t" then + return b1 * 256 + b2 + elseif ntype == "uint32_t" then + return b1 * 65536 * 256 + b2 * 65536 + b3 * 256 + b4 + elseif ntype == "int16_t" then + local n = b1 * 256 + b2 + local nn = (65536 - n) * -1 + if nn == -65536 then + nn = 0 + end + return nn + elseif ntype == "int32_t" then + local n = b1 * 65536 * 256 + b2 * 65536 + b3 * 256 + b4 + local nn = (4294967296 - n) * -1 + if nn == -4294967296 then + nn = 0 + end + return nn + elseif ntype == "double_t" then + local s = tostr(b8, b7, b6, b5, b4, b3, b2, b1) + double_decode_count = double_decode_count + 1 + local n = bytestodouble(s) + return n + else + error("unpack_number: not impl:" .. ntype) + end end - - local function unpacker_number(offset) - local obj_type = type_for( string.byte( strbuf, offset+1, offset+1 ) ) - local nlen = types_len_map[obj_type] - local ntype - if (obj_type == "float") then - error("float is not implemented") - else - ntype = obj_type .. "_t" - end - return offset+nlen+1,unpack_number(offset+1,ntype,nlen) + local obj_type = type_for(string.byte(strbuf, offset + 1, offset + 1)) + local nlen = types_len_map[obj_type] + local ntype + if obj_type == "float" then + error("float is not implemented") + else + ntype = obj_type .. "_t" + end + return offset + nlen + 1, unpack_number(offset + 1, ntype, nlen) end -local function unpack_map(offset,n) - local r = {} - local k,v - for _ = 1, n do - offset,k = unpackers.dynamic(offset) - assert(offset) - offset,v = unpackers.dynamic(offset) - assert(offset) - r[k] = v - end - return offset,r +local function unpack_map(offset, n) + local r = {} + local k, v + for _ = 1, n do + offset, k = unpackers.dynamic(offset) + assert(offset) + offset, v = unpackers.dynamic(offset) + assert(offset) + r[k] = v + end + return offset, r end -local function unpack_array(offset,n) - local r = {} - for i=1,n do - offset,r[i] = unpackers.dynamic(offset) - assert(offset) - end - return offset,r +local function unpack_array(offset, n) + local r = {} + for i = 1, n do + offset, r[i] = unpackers.dynamic(offset) + assert(offset) + end + return offset, r end function unpackers.dynamic(offset) - if offset >= #strbuf then error("need more data") end - local obj_type = type_for( string.byte( strbuf, offset+1, offset+1 ) ) - return unpackers[obj_type](offset) + if offset >= #strbuf then + error("need more data") + end + local obj_type = type_for(string.byte(strbuf, offset + 1, offset + 1)) + return unpackers[obj_type](offset) end function unpackers.undefined() - error("unimplemented:undefined") + error("unimplemented:undefined") end unpackers["nil"] = function(offset) - return offset+1,nil + return offset + 1, nil end unpackers["false"] = function(offset) - return offset+1,false + return offset + 1, false end unpackers["true"] = function(offset) - return offset+1,true + return offset + 1, true end unpackers.fixnum_posi = function(offset) - return offset+1, string.byte(strbuf, offset+1, offset+1) + return offset + 1, string.byte(strbuf, offset + 1, offset + 1) end unpackers.uint8 = function(offset) - return offset+2, string.byte(strbuf, offset+2, offset+2) + return offset + 2, string.byte(strbuf, offset + 2, offset + 2) end unpackers.uint16 = unpacker_number @@ -396,18 +436,18 @@ unpackers.uint32 = unpacker_number unpackers.uint64 = unpacker_number unpackers.fixnum_neg = function(offset) - -- alternative to cast below: - local n = string.byte( strbuf, offset+1, offset+1) - local nn = ( 256 - n ) * -1 - return offset+1, nn + -- alternative to cast below: + local n = string.byte(strbuf, offset + 1, offset + 1) + local nn = (256 - n) * -1 + return offset + 1, nn end unpackers.int8 = function(offset) - local i = string.byte( strbuf, offset+2, offset+2 ) - if i > 127 then - i = (256 - i ) * -1 - end - return offset+2, i + local i = string.byte(strbuf, offset + 2, offset + 2) + if i > 127 then + i = (256 - i) * -1 + end + return offset + 2, i end unpackers.int16 = unpacker_number @@ -418,92 +458,96 @@ unpackers.float = unpacker_number unpackers.double = unpacker_number unpackers.fixraw = function(offset) - local n = byte_mod( string.byte( strbuf, offset+1, offset+1) ,0x1f+1) - -- print("unpackers.fixraw: offset:", offset, "#buf:", #buf, "n:",n ) - local b - if ( #strbuf - 1 - offset ) < n then - error("require more data") - end + local n = byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x1f + 1) + -- print("unpackers.fixraw: offset:", offset, "#buf:", #buf, "n:",n ) + local b + if (#strbuf - 1 - offset) < n then + error("require more data") + end - if n > 0 then - b = string.sub( strbuf, offset + 1 + 1, offset + 1 + 1 + n - 1 ) - else - b = "" - end - return offset+n+1, b + if n > 0 then + b = string.sub(strbuf, offset + 1 + 1, offset + 1 + 1 + n - 1) + else + b = "" + end + return offset + n + 1, b end unpackers.raw16 = function(offset) - local n = unpack_number(offset+1,"uint16_t",2) - if ( #strbuf - 1 - 2 - offset ) < n then - error("require more data") - end - local b = string.sub( strbuf, offset+1+1+2, offset+1 + 1+2 + n - 1 ) - return offset+n+3, b + local n = unpack_number(offset + 1, "uint16_t", 2) + if (#strbuf - 1 - 2 - offset) < n then + error("require more data") + end + local b = string.sub(strbuf, offset + 1 + 1 + 2, offset + 1 + 1 + 2 + n - 1) + return offset + n + 3, b end unpackers.raw32 = function(offset) - local n = unpack_number(offset+1,"uint32_t",4) - if ( #strbuf - 1 - 4 - offset ) < n then - error( "require more data (possibly bug)") - end - local b = string.sub( strbuf, offset+1+ 1+4, offset+1 + 1+4 +n -1 ) - return offset+n+5,b + local n = unpack_number(offset + 1, "uint32_t", 4) + if (#strbuf - 1 - 4 - offset) < n then + error("require more data (possibly bug)") + end + local b = string.sub(strbuf, offset + 1 + 1 + 4, offset + 1 + 1 + 4 + n - 1) + return offset + n + 5, b end unpackers.fixarray = function(offset) - return unpack_array( offset+1,byte_mod( string.byte( strbuf, offset+1,offset+1),0x0f+1)) + return unpack_array(offset + 1, byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x0f + 1)) end unpackers.array16 = function(offset) - return unpack_array(offset+3,unpack_number(offset+1,"uint16_t",2)) + return unpack_array(offset + 3, unpack_number(offset + 1, "uint16_t", 2)) end unpackers.array32 = function(offset) - return unpack_array(offset+5,unpack_number(offset+1,"uint32_t",4)) + return unpack_array(offset + 5, unpack_number(offset + 1, "uint32_t", 4)) end unpackers.fixmap = function(offset) - return unpack_map(offset+1,byte_mod( string.byte( strbuf, offset+1,offset+1),0x0f+1)) + return unpack_map(offset + 1, byte_mod(string.byte(strbuf, offset + 1, offset + 1), 0x0f + 1)) end unpackers.map16 = function(offset) - return unpack_map(offset+3,unpack_number(offset+1,"uint16_t",2)) + return unpack_map(offset + 3, unpack_number(offset + 1, "uint16_t", 2)) end unpackers.map32 = function(offset) - return unpack_map(offset+5,unpack_number(offset+1,"uint32_t",4)) + return unpack_map(offset + 5, unpack_number(offset + 1, "uint32_t", 4)) end -- Main functions local ljp_pack = function(data) - strary={} - packers.dynamic(data) - local s = table.concat(strary,"") - return s + strary = {} + packers.dynamic(data) + local s = table.concat(strary, "") + return s end -local ljp_unpack = function(s,offset) - if offset == nil then offset = 0 end - if type(s) ~= "string" then return false,"invalid argument" end - local data - strbuf = s - offset,data = unpackers.dynamic(offset) - return offset,data +local ljp_unpack = function(s, offset) + if offset == nil then + offset = 0 + end + if type(s) ~= "string" then + return false, "invalid argument" + end + local data + strbuf = s + offset, data = unpackers.dynamic(offset) + return offset, data end local function ljp_stat() - return { - double_decode_count = double_decode_count, - double_encode_count = double_encode_count - } + return { + double_decode_count = double_decode_count, + double_encode_count = double_encode_count, + } end local msgpack = { - pack = ljp_pack, - unpack = ljp_unpack, - stat = ljp_stat + pack = ljp_pack, + unpack = ljp_unpack, + stat = ljp_stat, } return msgpack diff --git a/lua/deps/serpent.lua b/lua/deps/serpent.lua index a043713..4503686 100644 --- a/lua/deps/serpent.lua +++ b/lua/deps/serpent.lua @@ -1,140 +1,265 @@ local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" -local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} -local badtype = {thread = true, userdata = true, cdata = true} +local snum = { + [tostring(1 / 0)] = "1/0 --[[math.huge]]", + [tostring(-1 / 0)] = "-1/0 --[[-math.huge]]", + [tostring(0 / 0)] = "0/0", +} +local badtype = { thread = true, userdata = true, cdata = true } local getmetatable = debug and debug.getmetatable or getmetatable -local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ +local pairs = function(t) + return next, t +end -- avoid using __pairs in Lua 5.2+ local keyword, globals, G = {}, {}, (_G or _ENV) -for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', - 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', - 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end -for k,v in pairs(G) do globals[v] = k end -- build func to name mapping -for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do - for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end +for _, k in ipairs({ + "and", + "break", + "do", + "else", + "elseif", + "end", + "false", + "for", + "function", + "goto", + "if", + "in", + "local", + "nil", + "not", + "or", + "repeat", + "return", + "then", + "true", + "until", + "while", +}) do + keyword[k] = true +end +for k, v in pairs(G) do + globals[v] = k +end -- build func to name mapping +for _, g in ipairs({ "coroutine", "debug", "io", "math", "string", "table", "os" }) do + for k, v in pairs(type(G[g]) == "table" and G[g] or {}) do + globals[v] = g .. "." .. k + end +end local function s(t, opts) - local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum - local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge - local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) - local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring - local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) - local numformat = opts.numformat or "%.17g" - local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 - local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", - -- tostring(val) is needed because __tostring may return a non-string value - function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end - local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s)) - or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 - or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end - local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end - local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal - and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end - local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] - local n = name == nil and '' or name - local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] - local safe = plain and n or '['..safestr(n)..']' - return (path or '')..(plain and path and '.' or '')..safe, safe end - local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding - local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} - local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end - table.sort(k, function(a,b) - -- sort numeric keys first: k[key] is not nil for numerical keys - return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) - < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end - local function val2str(t, name, indent, insref, path, plainindex, level) - local ttype, level, mt = type(t), (level or 0), getmetatable(t) - local spath, sname = safename(path, name) - local tag = plainindex and - ((type(name) == "number") and '' or name..space..'='..space) or - (name ~= nil and sname..space..'='..space or '') - if seen[t] then -- already seen this element - sref[#sref+1] = spath..space..'='..space..seen[t] - return tag..'nil'..comment('ref', level) end - -- protect from those cases where __tostring may fail - if type(mt) == 'table' and metatostring ~= false then - local to, tr = pcall(function() return mt.__tostring(t) end) - local so, sr = pcall(function() return mt.__serialize(t) end) - if (to or so) then -- knows how to serialize itself - seen[t] = insref or spath - t = so and sr or tr - ttype = type(t) - end -- new value falls through to be serialized - end - if ttype == "table" then - if level >= maxl then return tag..'{}'..comment('maxlvl', level) end - seen[t] = insref or spath - if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty - if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end - local maxn, o, out = math.min(#t, maxnum or #t), {}, {} - for key = 1, maxn do o[key] = key end - if not maxnum or #o < maxnum then - local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables - for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end - if maxnum and #o > maxnum then o[maxnum+1] = nil end - if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end - local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) - for n, key in ipairs(o) do - local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse - if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing - or opts.keyallow and not opts.keyallow[key] - or opts.keyignore and opts.keyignore[key] - or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types - or sparse and value == nil then -- skipping nils; do nothing - elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then - if not seen[key] and not globals[key] then - sref[#sref+1] = 'placeholder' - local sname = safename(iname, gensym(key)) -- iname is table for local variables - sref[#sref] = val2str(key,sname,indent,sname,iname,true) end - sref[#sref+1] = 'placeholder' - local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']' - sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path)) - else - out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1) - if maxlen then - maxlen = maxlen - #out[#out] - if maxlen < 0 then break end - end - end - end - local prefix = string.rep(indent or '', level) - local head = indent and '{\n'..prefix..indent or '{' - local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) - local tail = indent and "\n"..prefix..'}' or '}' - return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level) - elseif badtype[ttype] then - seen[t] = insref or spath - return tag..globerr(t, level) - elseif ttype == 'function' then - seen[t] = insref or spath - if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end - local ok, res = pcall(string.dump, t) - local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level) - return tag..(func or globerr(t, level)) - else return tag..safestr(t) end -- handle all other types - end - local sepr = indent and "\n" or ";"..space - local body = val2str(t, name, indent) -- this call also populates sref - local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' - local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' - return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" + local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum + local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge + local space, maxl = (opts.compact and "" or " "), (opts.maxlevel or math.huge) + local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring + local iname, comm = "_" .. (name or ""), opts.comment and (tonumber(opts.comment) or math.huge) + local numformat = opts.numformat or "%.17g" + local seen, sref, syms, symn = {}, { "local " .. iname .. "={}" }, {}, 0 + local function gensym(val) + return "_" + .. ( + tostring(tostring(val)):gsub("[^%w]", ""):gsub( + "(%d%w+)", + -- tostring(val) is needed because __tostring may return a non-string value + function(s) + if not syms[s] then + symn = symn + 1 + syms[s] = symn + end + return tostring(syms[s]) + end + ) + ) + end + local function safestr(s) + return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s)) + or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 + or ("%q"):format(s):gsub("\010", "n"):gsub("\026", "\\026") + end + local function comment(s, l) + return comm and (l or 0) < comm and " --[[" .. select(2, pcall(tostring, s)) .. "]]" or "" + end + local function globerr(s, l) + return globals[s] and globals[s] .. comment(s, l) + or not fatal and safestr(select(2, pcall(tostring, s))) + or error("Can't serialize " .. tostring(s)) + end + local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] + local n = name == nil and "" or name + local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] + local safe = plain and n or "[" .. safestr(n) .. "]" + return (path or "") .. (plain and path and "." or "") .. safe, safe + end + local alphanumsort = type(opts.sortkeys) == "function" and opts.sortkeys + or function(k, o, n) -- k=keys, o=originaltable, n=padding + local maxn, to = tonumber(n) or 12, { number = "a", string = "b" } + local function padnum(d) + return ("%0" .. tostring(maxn) .. "d"):format(tonumber(d)) + end + table.sort(k, function(a, b) + -- sort numeric keys first: k[key] is not nil for numerical keys + return (k[a] ~= nil and 0 or to[type(a)] or "z") .. (tostring(a):gsub("%d+", padnum)) + < (k[b] ~= nil and 0 or to[type(b)] or "z") .. (tostring(b):gsub("%d+", padnum)) + end) + end + local function val2str(t, name, indent, insref, path, plainindex, level) + local ttype, level, mt = type(t), (level or 0), getmetatable(t) + local spath, sname = safename(path, name) + local tag = plainindex and ((type(name) == "number") and "" or name .. space .. "=" .. space) + or (name ~= nil and sname .. space .. "=" .. space or "") + if seen[t] then -- already seen this element + sref[#sref + 1] = spath .. space .. "=" .. space .. seen[t] + return tag .. "nil" .. comment("ref", level) + end + -- protect from those cases where __tostring may fail + if type(mt) == "table" and metatostring ~= false then + local to, tr = pcall(function() + return mt.__tostring(t) + end) + local so, sr = pcall(function() + return mt.__serialize(t) + end) + if to or so then -- knows how to serialize itself + seen[t] = insref or spath + t = so and sr or tr + ttype = type(t) + end -- new value falls through to be serialized + end + if ttype == "table" then + if level >= maxl then + return tag .. "{}" .. comment("maxlvl", level) + end + seen[t] = insref or spath + if next(t) == nil then + return tag .. "{}" .. comment(t, level) + end -- table empty + if maxlen and maxlen < 0 then + return tag .. "{}" .. comment("maxlen", level) + end + local maxn, o, out = math.min(#t, maxnum or #t), {}, {} + for key = 1, maxn do + o[key] = key + end + if not maxnum or #o < maxnum then + local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables + for key in pairs(t) do + if o[key] ~= key then + n = n + 1 + o[n] = key + end + end + end + if maxnum and #o > maxnum then + o[maxnum + 1] = nil + end + if opts.sortkeys and #o > maxn then + alphanumsort(o, t, opts.sortkeys) + end + local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) + for n, key in ipairs(o) do + local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse + if + opts.valignore and opts.valignore[value] -- skip ignored values; do nothing + or opts.keyallow and not opts.keyallow[key] + or opts.keyignore and opts.keyignore[key] + or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types + or sparse and value == nil + then -- skipping nils; do nothing + elseif ktype == "table" or ktype == "function" or badtype[ktype] then + if not seen[key] and not globals[key] then + sref[#sref + 1] = "placeholder" + local sname = safename(iname, gensym(key)) -- iname is table for local variables + sref[#sref] = val2str(key, sname, indent, sname, iname, true) + end + sref[#sref + 1] = "placeholder" + local path = seen[t] .. "[" .. tostring(seen[key] or globals[key] or gensym(key)) .. "]" + sref[#sref] = path + .. space + .. "=" + .. space + .. tostring(seen[value] or val2str(value, nil, indent, path)) + else + out[#out + 1] = val2str(value, key, indent, nil, seen[t], plainindex, level + 1) + if maxlen then + maxlen = maxlen - #out[#out] + if maxlen < 0 then + break + end + end + end + end + local prefix = string.rep(indent or "", level) + local head = indent and "{\n" .. prefix .. indent or "{" + local body = table.concat(out, "," .. (indent and "\n" .. prefix .. indent or space)) + local tail = indent and "\n" .. prefix .. "}" or "}" + return (custom and custom(tag, head, body, tail, level) or tag .. head .. body .. tail) .. comment(t, level) + elseif badtype[ttype] then + seen[t] = insref or spath + return tag .. globerr(t, level) + elseif ttype == "function" then + seen[t] = insref or spath + if opts.nocode then + return tag .. "function() --[[..skipped..]] end" .. comment(t, level) + end + local ok, res = pcall(string.dump, t) + local func = ok and "((loadstring or load)(" .. safestr(res) .. ",'@serialized'))" .. comment(t, level) + return tag .. (func or globerr(t, level)) + else + return tag .. safestr(t) + end -- handle all other types + end + local sepr = indent and "\n" or ";" .. space + local body = val2str(t, name, indent) -- this call also populates sref + local tail = #sref > 1 and table.concat(sref, sepr) .. sepr or "" + local warn = opts.comment and #sref > 1 and space .. "--[[incomplete output with shared/self-references skipped]]" + or "" + return not name and body .. warn or "do local " .. body .. sepr .. tail .. "return " .. name .. sepr .. "end" end local function deserialize(data, opts) - local env = (opts and opts.safe == false) and G - or setmetatable({}, { - __index = function(t,k) return t end, - __call = function(t,...) error("cannot call functions") end - }) - local f, res = (loadstring or load)('return '..data, nil, nil, env) - if not f then f, res = (loadstring or load)(data, nil, nil, env) end - if not f then return f, res end - if setfenv then setfenv(f, env) end - return pcall(f) + local env = (opts and opts.safe == false) and G + or setmetatable({}, { + __index = function(t, k) + return t + end, + __call = function(t, ...) + error("cannot call functions") + end, + }) + local f, res = (loadstring or load)("return " .. data, nil, nil, env) + if not f then + f, res = (loadstring or load)(data, nil, nil, env) + end + if not f then + return f, res + end + if setfenv then + setfenv(f, env) + end + return pcall(f) end -local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end -return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, - load = deserialize, - dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, - line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, - block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } +local function merge(a, b) + if b then + for k, v in pairs(b) do + a[k] = v + end + end + return a +end +return { + _NAME = n, + _COPYRIGHT = c, + _DESCRIPTION = d, + _VERSION = v, + serialize = s, + load = deserialize, + dump = function(a, opts) + return s(a, merge({ name = "_", compact = true, sparse = true }, opts)) + end, + line = function(a, opts) + return s(a, merge({ sortkeys = true, comment = true }, opts)) + end, + block = function(a, opts) + return s(a, merge({ indent = " ", sortkeys = true, comment = true }, opts)) + end, +} diff --git a/lua/deps/struct.lua b/lua/deps/struct.lua index 54b32d4..1b0e814 100644 --- a/lua/deps/struct.lua +++ b/lua/deps/struct.lua @@ -1,179 +1,178 @@ local struct = {} function struct.pack(format, ...) - local stream = {} - local vars = {...} - local endianness = true + local stream = {} + local vars = { ... } + local endianness = true - for i = 1, format:len() do - local opt = format:sub(i, i) + 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 val = tonumber(table.remove(vars, 1)) + 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 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 + 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 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 + 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 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 + 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))) + 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 + 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) + return table.concat(stream) end function struct.unpack(format, stream, pos) - local vars = {} - local iterator = pos or 1 - local endianness = true + local vars = {} + local iterator = pos or 1 + local endianness = true - for i = 1, format:len() do - local opt = format:sub(i, i) + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + table.insert(vars, stream:sub(iterator, iterator + len - 1)) + iterator = iterator + len + end + end - return unpack(vars) + return unpack(vars) end return struct diff --git a/lua/lib/log.lua b/lua/lib/log.lua index b7b71e4..dcacdb3 100644 --- a/lua/lib/log.lua +++ b/lua/lib/log.lua @@ -2,37 +2,37 @@ local Log = {} Log.codes = {} Log.levels = { - { "debug", "Comment" }, - { "info", "None" }, - { "warn", "WarningMsg" }, - { "error", "ErrorMsg" }, + { "debug", "Comment" }, + { "info", "None" }, + { "warn", "WarningMsg" }, + { "error", "ErrorMsg" }, } function Log:init(options) - self.level = options.level - return self + self.level = options.level + return self end -- Initialize logger with log functions for each level for i = 1, #Log.levels do - local level, hl = unpack(Log.levels[i]) + local level, hl = unpack(Log.levels[i]) - Log.codes[level] = 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.level or self.codes[level] < self.codes[self.level] or type(message) ~= "string" then - return - end + 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.level or self.codes[level] < self.codes[self.level] or type(message) ~= "string" then + return + end - vim.schedule(function() - local escaped_message = vim.fn.escape(message, '"'):gsub("\n", "\\n") + vim.schedule(function() + local escaped_message = vim.fn.escape(message, '"'):gsub("\n", "\\n") - vim.cmd(string.format("echohl %s", hl)) - vim.cmd(string.format([[echom "[%s] %s"]], "presence.nvim", escaped_message)) - vim.cmd("echohl NONE") - end) - end + vim.cmd(string.format("echohl %s", hl)) + vim.cmd(string.format([[echom "[%s] %s"]], "presence.nvim", escaped_message)) + vim.cmd("echohl NONE") + end) + end end return Log diff --git a/lua/presence/discord.lua b/lua/presence/discord.lua index 83fd77a..6ab2f55 100644 --- a/lua/presence/discord.lua +++ b/lua/presence/discord.lua @@ -1,9 +1,9 @@ local Discord = {} Discord.opcodes = { - auth = 0, - frame = 1, - closed = 2, + auth = 0, + frame = 1, + closed = 2, } -- Discord RPC Subscription events @@ -11,190 +11,187 @@ Discord.opcodes = { -- Ready: https://discord.com/developers/docs/topics/rpc#ready -- Error: https://discord.com/developers/docs/topics/rpc#error Discord.events = { - READY = "READY", - ERROR = "ERROR", + READY = "READY", + ERROR = "ERROR", } local struct = require("deps.struct") -- Initialize a new Discord RPC client function Discord:init(options) - self.log = options.logger - self.client_id = options.client_id - self.ipc_socket = options.ipc_socket + self.log = options.logger + self.client_id = options.client_id + self.ipc_socket = options.ipc_socket - self.pipe = vim.loop.new_pipe(false) + self.pipe = vim.loop.new_pipe(false) - return self + 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(false) - end + if self.pipe:is_closing() then + self.pipe = vim.loop.new_pipe(false) + end - self.pipe:connect(self.ipc_socket, on_connect) + self.pipe:connect(self.ipc_socket, on_connect) end function Discord:is_connected() - return self.pipe:is_active() + 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 + 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(success, body) - if not success then - self.log:warn(string.format("Failed to encode payload: %s", vim.inspect(body))) - return - end + self.encode_json(payload, function(success, body) + if not success then + self.log:warn(string.format("Failed to encode payload: %s", vim.inspect(body))) + return + end - -- Start reading for the response - self.pipe:read_start(function(...) - self:read_message(payload.nonce, on_response, ...) - end) + -- 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("<ii", opcode, #body)..body + -- Construct message denoting little endian, auth opcode, msg length + local message = struct.pack("<ii", opcode, #body) .. body - -- Write the message to the pipe - self.pipe:write(message, function(err) - if err then - local err_format = "Pipe write error - %s" - local err_message = string.format(err_format, err) + -- Write the message to the pipe + self.pipe:write(message, function(err) + if err then + local err_format = "Pipe write error - %s" + local err_message = string.format(err_format, err) - on_response(err_message) - else - self.log:debug("Wrote message to pipe") - end - end) - end) + on_response(err_message) + else + self.log:debug("Wrote message to pipe") + end + end) + end) end -- Read and handle socket messages function Discord:read_message(nonce, on_response, err, chunk) - if err then - local err_format = "Pipe read error - %s" - local err_message = string.format(err_format, err) + if err then + local err_format = "Pipe read error - %s" + local err_message = string.format(err_format, err) - on_response(err_message) + on_response(err_message) + elseif chunk then + -- Strip header from the chunk + local message = chunk:match("({.+)") + local response_opcode = struct.unpack("<ii", chunk) - elseif chunk then - -- Strip header from the chunk - local message = chunk:match("({.+)") - local response_opcode = struct.unpack("<ii", chunk) + self.decode_json(message, function(success, response) + -- Check for a non-frame opcode in the response + if response_opcode ~= self.opcodes.frame then + local err_format = "Received unexpected opcode - %s (code %s)" + local err_message = string.format(err_format, response.message, response.code) - self.decode_json(message, function(success, response) - -- Check for a non-frame opcode in the response - if response_opcode ~= self.opcodes.frame then - local err_format = "Received unexpected opcode - %s (code %s)" - local err_message = string.format(err_format, response.message, response.code) + return on_response(err_message) + end - return on_response(err_message) - end + -- Unable to decode the response + if not success then + -- Indetermine state at this point, no choice but to simply warn on the parse failure + -- but invoke empty response callback as request may still have succeeded + self.log:warn(string.format("Failed to decode payload: %s", vim.inspect(message))) + return on_response() + end - -- Unable to decode the response - if not success then - -- Indetermine state at this point, no choice but to simply warn on the parse failure - -- but invoke empty response callback as request may still have succeeded - self.log:warn(string.format("Failed to decode payload: %s", vim.inspect(message))) - return on_response() - end + -- Check for an error event response + if response.evt == self.events.ERROR then + local data = response.data + local err_format = "Received error event - %s (code %s)" + local err_message = string.format(err_format, data.message, data.code) - -- Check for an error event response - if response.evt == self.events.ERROR then - local data = response.data - local err_format = "Received error event - %s (code %s)" - local err_message = string.format(err_format, data.message, data.code) + return on_response(err_message) + end - return on_response(err_message) - end + -- Check for a valid nonce value + if response.nonce and response.nonce ~= vim.NIL and response.nonce ~= nonce then + local err_format = "Received unexpected nonce - %s (expected %s)" + local err_message = string.format(err_format, response.nonce, nonce) - -- Check for a valid nonce value - if response.nonce and response.nonce ~= vim.NIL and response.nonce ~= nonce then - local err_format = "Received unexpected nonce - %s (expected %s)" - local err_message = string.format(err_format, response.nonce, nonce) + return on_response(err_message) + end - return on_response(err_message) - end - - on_response(nil, response) - end) - else - -- TODO: Handle when pipe is closed - self.log:warn("Pipe was closed") - end + on_response(nil, response) + end) + else + -- TODO: Handle when pipe is closed + self.log:warn("Pipe was closed") + end end -- Call to authorize the client connection with Discord -- Callback argument in format: on_authorize(error[, response_table]) function Discord:authorize(on_authorize) - local payload = { - client_id = self.client_id, - v = 1, - } + local payload = { + client_id = self.client_id, + v = 1, + } - self:call(self.opcodes.auth, payload, on_authorize) + self:call(self.opcodes.auth, payload, on_authorize) end -- Call to set the Neovim activity to Discord function Discord:set_activity(activity, on_response) - local payload = { - cmd = "SET_ACTIVITY", - nonce = self.generate_uuid(), - args = { - activity = activity, - pid = vim.loop:os_getpid(), - }, - } + local payload = { + cmd = "SET_ACTIVITY", + nonce = self.generate_uuid(), + args = { + activity = activity, + pid = vim.loop:os_getpid(), + }, + } - self:call(self.opcodes.frame, payload, on_response) + self:call(self.opcodes.frame, payload, on_response) end function Discord.generate_uuid(seed) - local index = 0 - local template ="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + local index = 0 + local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" - local uuid = template:gsub("[xy]", function(char) - -- Increment an index to seed per char - index = index + 1 - math.randomseed((seed or os.clock()) / index) + local uuid = template:gsub("[xy]", function(char) + -- Increment an index to seed per char + index = index + 1 + math.randomseed((seed or os.clock()) / index) - local n = char == "x" - and math.random(0, 0xf) - or math.random(8, 0xb) + local n = char == "x" and math.random(0, 0xf) or math.random(8, 0xb) - return string.format("%x", n) - end) + return string.format("%x", n) + end) - return uuid + return uuid end function Discord.decode_json(t, on_done) - vim.schedule(function() - on_done(pcall(function() - return vim.fn.json_decode(t) - end)) - end) + vim.schedule(function() + on_done(pcall(function() + return vim.fn.json_decode(t) + end)) + end) end function Discord.encode_json(t, on_done) - vim.schedule(function() - on_done(pcall(function() - return vim.fn.json_encode(t) - end)) - end) + vim.schedule(function() + on_done(pcall(function() + return vim.fn.json_encode(t) + end)) + end) end return Discord diff --git a/lua/presence/file_assets.lua b/lua/presence/file_assets.lua index f906a28..9ecfbb6 100644 --- a/lua/presence/file_assets.lua +++ b/lua/presence/file_assets.lua @@ -11,266 +11,266 @@ -- go = { "Go", "https://go.dev/blog/go-brand/Go-Logo/PNG/Go-Logo_Aqua.png" }, -- } return { - [".aliases"] = { ".aliases", "shell" }, - [".appveyor.yml"] = { "AppVeyor config", "appveyor" }, - [".babelrc"] = { "Babel config", "babel" }, - [".babelrc.cjs"] = { "Babel config", "babel" }, - [".babelrc.js"] = { "Babel config", "babel" }, - [".babelrc.json"] = { "Babel config", "babel" }, - [".babelrc.mjs"] = { "Babel config", "babel" }, - [".bash_login"] = { ".bash_login", "shell" }, - [".bash_logout"] = { ".bash_logout", "shell" }, - [".bash_profile"] = { ".bash_profile", "shell" }, - [".bash_prompt"] = { ".bash_prompt", "shell" }, - [".bashrc"] = { ".bashrc", "shell" }, - [".cshrc"] = { ".cshrc", "shell" }, - [".dockercfg"] = { "Docker", "docker" }, - [".dockerfile"] = { "Docker", "docker" }, - [".dockerignore"] = { "Docker", "docker" }, - [".editorconfig"] = { "EditorConfig", "editorconfig" }, - [".eslintignore"] = { "ESLint", "eslint" }, - [".eslintrc"] = { "ESLint", "eslint" }, - [".eslintrc.cjs"] = { "ESLint", "eslint" }, - [".eslintrc.js"] = { "ESLint", "eslint" }, - [".eslintrc.json"] = { "ESLint", "eslint" }, - [".eslintrc.yaml"] = { "ESLint", "eslint" }, - [".eslintrc.yml"] = { "ESLint", "eslint" }, - [".gitattributes"] = { "git", "git" }, - [".gitconfig"] = { "git", "git" }, - [".gitignore"] = { "git", "git" }, - [".gitlab-ci.yaml"] = { "GitLab CI", "gitlab" }, - [".gitlab-ci.yml"] = { "GitLab CI", "gitlab" }, - [".gitmodules"] = { "git", "git" }, - [".login"] = { ".login", "shell" }, - [".logout"] = { ".login", "shell" }, - [".luacheckrc"] = { ".luacheckrc", "lua" }, - [".npmignore"] = { "npm config", "npm" }, - [".npmrc"] = { "npm config", "npm" }, - [".nvmrc"] = { ".nvmrc", "nodejs" }, - [".prettierrc"] = { "Prettier", "prettier" }, - [".prettierrc.cjs"] = { "Prettier", "prettier" }, - [".prettierrc.js"] = { "Prettier", "prettier" }, - [".prettierrc.json"] = { "Prettier", "prettier" }, - [".prettierrc.json5"] = { "Prettier", "prettier" }, - [".prettierrc.toml"] = { "Prettier", "prettier" }, - [".prettierrc.yaml"] = { "Prettier", "prettier" }, - [".prettierrc.yml"] = { "Prettier", "prettier" }, - [".profile"] = { ".profile", "shell" }, - [".tcshrc"] = { ".tcshrc", "shell" }, - [".terraformrc"] = { "Terraform config", "terraform" }, - [".tmux.conf"] = { "tmux", "tmux" }, - [".travis.yml"] = { "Travis CI", "travis" }, - [".vimrc"] = { ".vimrc", "vim" }, - [".watchmanconfig"] = { "Watchman config", "watchman" }, - [".yarnrc"] = { "Yarn config", "yarn" }, - [".zlogin"] = { ".zlogin", "shell" }, - [".zprofile"] = { ".zprofile", "shell" }, - [".zshenv"] = { ".zshenv", "shell" }, - [".zshrc"] = { ".zshrc", "shell" }, - ["Brewfile"] = { "Brewfile", "homebrew" }, - ["Brewfile.lock.json"] = { "Brewfile.lock.json", "homebrew" }, - ["CHANGELOG"] = { "CHANGELOG", "text" }, - ["CODE_OF_CONDUCT"] = { "Code of Conduct", "text" }, - ["COMMIT_EDITMSG"] = { "git", "git" }, - ["CONTRIBUTING"] = { "CONTRIBUTING", "text" }, - ["Cargo.lock"] = { "Cargo lockfile", "cargo" }, - ["Cargo.toml"] = { "Cargo.toml", "cargo" }, - ["Dockerfile"] = { "Docker", "docker" }, - ["Gemfile"] = { "Gemfile", "ruby" }, - ["Gemfile.lock"] = { "Gemfile lockfile", "ruby" }, - ["LICENSE"] = { "LICENSE", "text" }, - ["Makefile"] = { "Makefile", "code" }, - ["Rakefile"] = { "Rakefile", "ruby" }, - ["abookrc"] = { "abook", "abook" }, - ["alacritty.yaml"] = { "Alacritty config", "alacritty" }, - ["alacritty.yml"] = { "Alacritty config", "alacritty" }, - ["appveyor.yml"] = { "AppVeyor config", "appveyor" }, - ["babel.config.cjs"] = { "Babel config", "babel" }, - ["babel.config.js"] = { "Babel config", "babel" }, - ["babel.config.json"] = { "Babel config", "babel" }, - ["babel.config.mjs"] = { "Babel config", "babel" }, - ["brew.sh"] = { "brew.sh", "homebrew" }, - ["docker-compose.yaml"] = { "Docker", "docker" }, - ["docker-compose.yml"] = { "Docker", "docker" }, - ["gitconfig"] = { "git", "git" }, - ["gitlab.rb"] = { "GitLab config", "gitlab" }, - ["gitlab.yml"] = { "GitLab config", "gitlab" }, - ["go.mod"] = { "go.mod", "go" }, - ["go.sum"] = { "go.sum", "go" }, - ["jest.config.js"] = { "Jest config", "jest" }, - ["jest.setup.js"] = { "Jest config", "jest" }, - ["jest.setup.ts"] = { "Jest config", "jest" }, - ["kitty.conf"] = { "Kitty config", "kitty" }, - ["next-env.d.ts"] = { "Next.js config", "nextjs" }, - ["next.config.js"] = { "Next.js config", "nextjs" }, - ["nginx"] = { "NGINX", "nginx" }, - ["nginx.conf"] = { "NGINX", "nginx" }, - ["nuxt.config.js"] = { "Nuxt config", "nuxtjs" }, - ["prettier.config.cjs"] = { "Prettier", "prettier" }, - ["prettier.config.js"] = { "Prettier", "prettier" }, - ["profile"] = { "profile", "shell" }, - ["renovate.json"] = { "Renovate config", "renovate" }, - ["requirements.txt"] = { "requirements.txt", "python" }, - ["tailwind.config.js"] = { "Tailwind", "tailwind" }, - ["terraform.rc"] = { "Terraform config", "terraform" }, - ["v.mod"] = { "v.mod", "vlang" }, - ["watchman.json"] = { "Watchman config", "watchman" }, - ["webpack.config.js"] = { "Webpack", "webpack" }, - ["webpack.config.ts"] = { "Webpack", "webpack" }, - ["yarn.lock"] = { "Yarn lockfile", "yarn" }, - ["zlogin"] = { "zlogin", "shell" }, - ["zlogout"] = { "zlogout", "shell" }, - ["zprofile"] = { "zprofile", "shell" }, - ["zshenv"] = { "zshenv", "shell" }, - ["zshrc"] = { "zshrc", "shell" }, - addressbook = { "abook", "abook" }, - ahk = { "Autohotkey", "autohotkey" }, - applescript = { "Applescript", "applescript" }, - bash = { "Bash script", "shell" }, - bib = { "BibTeX", "latex" }, - c = { "C ", "c" }, - cabal = { "Cabal file", "haskell" }, - cc = { "C++", "c_plus_plus" }, - cf = { "Configuration file", "config" }, - cfg = { "Configuration file", "config" }, - cl = { "Common Lisp", "lisp" }, - clj = { "Clojure", "clojure" }, - cljs = { "ClojureScript", "clojurescript" }, - cls = { "Visual Basic class module", "visual_basic" }, - cnf = { "Configuration file", "config" }, - coffee = { "CoffeeScript", "coffeescript" }, - conf = { "Configuration file", "config" }, - config = { "Configuration file", "config" }, - cpp = { "C++", "c_plus_plus" }, - cr = { "Crystal", "crystal" }, - cs = { "C#", "c_sharp" }, - css = { "CSS", "css" }, - cxx = { "C++", "c_plus_plus" }, - d = { "D", "d" }, - dart = { "Dart", "dart" }, - dll = { "DLL file", "visual_basic" }, - e = { "Eiffel", "eiffel" }, - elm = { "Elm", "elm" }, - erl = { "Erlang", "erlang" }, - ex = { "Elixir", "elixir" }, - expect = { "Expect", "tcl" }, - fasl = { "Common Lisp", "lisp" }, - fish = { "Fish script", "fish" }, - fnl = { "Fennel", "fennel" }, - fs = { "F#", "f_sharp" }, - g = { "ANTLR grammar", "antlr" }, - g3 = { "ANTLR 3 grammar", "antlr" }, - g4 = { "ANTLR 4 grammar", "antlr" }, - gemspec = { "Gem Spec", "ruby" }, - go = { "Go", "go" }, - gql = { "GraphQL", "graphql" }, - graphql = { "GraphQL", "graphql" }, - groovy = { "Groovy", "groovy" }, - gsh = { "Groovy", "groovy" }, - gvy = { "Groovy", "groovy" }, - gy = { "Groovy", "groovy" }, - h = { "C header file", "c" }, - hack = { "Hack", "hack" }, - haml = { "Haml", "haml" }, - hpp = { "C++ header file", "c_plus_plus" }, - hs = { "Haskell", "haskell" }, - html = { "HTML", "html" }, - hx = { "Haxe", "haxe" }, - hxx = { "C++ header file", "c_plus_plus" }, - idr = { "Idris", "idris" }, - ini = { "Configuration file", "config" }, - ino = { "Arduino", "arduino" }, - ipynb = { "Jupyter Notebook", "jupyter" }, - java = { "Java", "java" }, - jl = { "Julia", "julia" }, - js = { "JavaScript", "javascript" }, - json = { "JSON", "json" }, - jsx = { "React", "react" }, - ksh = { "KornShell script", "shell" }, - kshrc = { "KornShell config", "shell" }, - kt = { "Kotlin", "kotlin" }, - kv = { "Kivy", "kivy" }, - l = { "Common Lisp", "lisp" }, - less = { "Less", "less" }, - lidr = { "Idris", "idris" }, - liquid = { "Liquid", "liquid" }, - lisp = { "Common Lisp", "lisp" }, - log = { "Log file", "code" }, - lsp = { "Common Lisp", "lisp" }, - lua = { "Lua", "lua" }, - m = { "MATLAB", "matlab" }, - markdown = { "Markdown", "markdown" }, - mat = { "MATLAB", "matlab" }, - md = { "Markdown", "markdown" }, - mdx = { "MDX", "mdx" }, - mjs = { "JavaScript", "javascript" }, - ml = { "OCaml", "ocaml" }, - nim = { "Nim", "nim" }, - nix = { "Nix", "nix" }, - norg = { "Neorg", "neorg" }, - org = { "Org", "org" }, - pb = { "Protobuf", "protobuf" }, - pcss = { "PostCSS", "postcss" }, - pgsql = { "PostgreSQL", "pgsql" }, - php = { "PHP", "php" }, - pl = { "Perl", "perl" }, - plist = { "Property List", "markup" }, - postcss = { "PostCSS", "postcss" }, - proto = { "Protobuf", "protobuf" }, - ps1 = { "PowerShell", "powershell" }, - psd1 = { "PowerShell", "powershell" }, - psm1 = { "PowerShell", "powershell" }, - purs = { "PureScript", "purescript" }, - py = { "Python", "python" }, - r = { "R", "r" }, - raku = { "Raku", "raku" }, - rakudoc = { "Raku", "raku" }, - rakumod = { "Raku", "raku" }, - rakutest = { "Raku", "raku" }, - rb = { "Ruby", "ruby" }, - re = { "Reason", "reason" }, - res = { "ReScript", "rescript" }, - rkt = { "Racket", "racket"}, - rs = { "Rust", "rust" }, - sass = { "Sass", "sass" }, - scala = { "Scala", "scala" }, - scm = { "Scheme", "scheme" }, - scss = { "Sass", "scss" }, - sh = { "Shell script", "shell" }, - shrc = { "Shell config", "shell" }, - snap = { "Jest Snapshot", "jest" }, - sql = { "SQL", "database" }, - ss = { "Scheme", "scheme" }, - svelte = { "Svelte", "svelte" }, - svg = { "SVG", "markup" }, - swift = { "Swift", "swift" }, - tcl = { "Tcl", "tcl" }, - tex = { "LaTeX", "latex" }, - text = { "Text file", "text" }, - tf = { "Terraform", "terraform" }, - tk = { "Tcl/Tk", "tcl" }, - tl = { "Teal", "teal" }, - toml = { "TOML", "toml" }, - ts = { "TypeScript", "typescript" }, - tsx = { "React", "react" }, - txt = { "Text file", "text" }, - uc = { "UnrealScript", "unreal" }, - v = { "Vlang", "vlang" }, - vsh = { "Vlang shell script", "vlang" }, - vb = { "Visual Basic", "visual_basic" }, - vbp = { "Visual Basic project file", "visual_basic" }, - vim = { "Vim", "vim" }, - viml = { "Vim", "vim" }, - vue = { "Vue", "vue" }, - wasm = { "WebAssembly", "webassembly" }, - wast = { "WebAssembly", "webassembly" }, - wat = { "WebAssembly", "webassembly" }, - xml = { "XML", "markup" }, - xsd = { "XML Schema", "markup" }, - xslt = { "XSLT", "markup" }, - yaml = { "YAML", "yaml" }, - yml = { "YAML", "yaml" }, - zig = { "Zig", "zig" }, - zsh = { "Zsh script", "shell" }, - zu = { "Zimbu", "zimbu" }, + [".aliases"] = { ".aliases", "shell" }, + [".appveyor.yml"] = { "AppVeyor config", "appveyor" }, + [".babelrc"] = { "Babel config", "babel" }, + [".babelrc.cjs"] = { "Babel config", "babel" }, + [".babelrc.js"] = { "Babel config", "babel" }, + [".babelrc.json"] = { "Babel config", "babel" }, + [".babelrc.mjs"] = { "Babel config", "babel" }, + [".bash_login"] = { ".bash_login", "shell" }, + [".bash_logout"] = { ".bash_logout", "shell" }, + [".bash_profile"] = { ".bash_profile", "shell" }, + [".bash_prompt"] = { ".bash_prompt", "shell" }, + [".bashrc"] = { ".bashrc", "shell" }, + [".cshrc"] = { ".cshrc", "shell" }, + [".dockercfg"] = { "Docker", "docker" }, + [".dockerfile"] = { "Docker", "docker" }, + [".dockerignore"] = { "Docker", "docker" }, + [".editorconfig"] = { "EditorConfig", "editorconfig" }, + [".eslintignore"] = { "ESLint", "eslint" }, + [".eslintrc"] = { "ESLint", "eslint" }, + [".eslintrc.cjs"] = { "ESLint", "eslint" }, + [".eslintrc.js"] = { "ESLint", "eslint" }, + [".eslintrc.json"] = { "ESLint", "eslint" }, + [".eslintrc.yaml"] = { "ESLint", "eslint" }, + [".eslintrc.yml"] = { "ESLint", "eslint" }, + [".gitattributes"] = { "git", "git" }, + [".gitconfig"] = { "git", "git" }, + [".gitignore"] = { "git", "git" }, + [".gitlab-ci.yaml"] = { "GitLab CI", "gitlab" }, + [".gitlab-ci.yml"] = { "GitLab CI", "gitlab" }, + [".gitmodules"] = { "git", "git" }, + [".login"] = { ".login", "shell" }, + [".logout"] = { ".login", "shell" }, + [".luacheckrc"] = { ".luacheckrc", "lua" }, + [".npmignore"] = { "npm config", "npm" }, + [".npmrc"] = { "npm config", "npm" }, + [".nvmrc"] = { ".nvmrc", "nodejs" }, + [".prettierrc"] = { "Prettier", "prettier" }, + [".prettierrc.cjs"] = { "Prettier", "prettier" }, + [".prettierrc.js"] = { "Prettier", "prettier" }, + [".prettierrc.json"] = { "Prettier", "prettier" }, + [".prettierrc.json5"] = { "Prettier", "prettier" }, + [".prettierrc.toml"] = { "Prettier", "prettier" }, + [".prettierrc.yaml"] = { "Prettier", "prettier" }, + [".prettierrc.yml"] = { "Prettier", "prettier" }, + [".profile"] = { ".profile", "shell" }, + [".tcshrc"] = { ".tcshrc", "shell" }, + [".terraformrc"] = { "Terraform config", "terraform" }, + [".tmux.conf"] = { "tmux", "tmux" }, + [".travis.yml"] = { "Travis CI", "travis" }, + [".vimrc"] = { ".vimrc", "vim" }, + [".watchmanconfig"] = { "Watchman config", "watchman" }, + [".yarnrc"] = { "Yarn config", "yarn" }, + [".zlogin"] = { ".zlogin", "shell" }, + [".zprofile"] = { ".zprofile", "shell" }, + [".zshenv"] = { ".zshenv", "shell" }, + [".zshrc"] = { ".zshrc", "shell" }, + ["Brewfile"] = { "Brewfile", "homebrew" }, + ["Brewfile.lock.json"] = { "Brewfile.lock.json", "homebrew" }, + ["CHANGELOG"] = { "CHANGELOG", "text" }, + ["CODE_OF_CONDUCT"] = { "Code of Conduct", "text" }, + ["COMMIT_EDITMSG"] = { "git", "git" }, + ["CONTRIBUTING"] = { "CONTRIBUTING", "text" }, + ["Cargo.lock"] = { "Cargo lockfile", "cargo" }, + ["Cargo.toml"] = { "Cargo.toml", "cargo" }, + ["Dockerfile"] = { "Docker", "docker" }, + ["Gemfile"] = { "Gemfile", "ruby" }, + ["Gemfile.lock"] = { "Gemfile lockfile", "ruby" }, + ["LICENSE"] = { "LICENSE", "text" }, + ["Makefile"] = { "Makefile", "code" }, + ["Rakefile"] = { "Rakefile", "ruby" }, + ["abookrc"] = { "abook", "abook" }, + ["alacritty.yaml"] = { "Alacritty config", "alacritty" }, + ["alacritty.yml"] = { "Alacritty config", "alacritty" }, + ["appveyor.yml"] = { "AppVeyor config", "appveyor" }, + ["babel.config.cjs"] = { "Babel config", "babel" }, + ["babel.config.js"] = { "Babel config", "babel" }, + ["babel.config.json"] = { "Babel config", "babel" }, + ["babel.config.mjs"] = { "Babel config", "babel" }, + ["brew.sh"] = { "brew.sh", "homebrew" }, + ["docker-compose.yaml"] = { "Docker", "docker" }, + ["docker-compose.yml"] = { "Docker", "docker" }, + ["gitconfig"] = { "git", "git" }, + ["gitlab.rb"] = { "GitLab config", "gitlab" }, + ["gitlab.yml"] = { "GitLab config", "gitlab" }, + ["go.mod"] = { "go.mod", "go" }, + ["go.sum"] = { "go.sum", "go" }, + ["jest.config.js"] = { "Jest config", "jest" }, + ["jest.setup.js"] = { "Jest config", "jest" }, + ["jest.setup.ts"] = { "Jest config", "jest" }, + ["kitty.conf"] = { "Kitty config", "kitty" }, + ["next-env.d.ts"] = { "Next.js config", "nextjs" }, + ["next.config.js"] = { "Next.js config", "nextjs" }, + ["nginx"] = { "NGINX", "nginx" }, + ["nginx.conf"] = { "NGINX", "nginx" }, + ["nuxt.config.js"] = { "Nuxt config", "nuxtjs" }, + ["prettier.config.cjs"] = { "Prettier", "prettier" }, + ["prettier.config.js"] = { "Prettier", "prettier" }, + ["profile"] = { "profile", "shell" }, + ["renovate.json"] = { "Renovate config", "renovate" }, + ["requirements.txt"] = { "requirements.txt", "python" }, + ["tailwind.config.js"] = { "Tailwind", "tailwind" }, + ["terraform.rc"] = { "Terraform config", "terraform" }, + ["v.mod"] = { "v.mod", "vlang" }, + ["watchman.json"] = { "Watchman config", "watchman" }, + ["webpack.config.js"] = { "Webpack", "webpack" }, + ["webpack.config.ts"] = { "Webpack", "webpack" }, + ["yarn.lock"] = { "Yarn lockfile", "yarn" }, + ["zlogin"] = { "zlogin", "shell" }, + ["zlogout"] = { "zlogout", "shell" }, + ["zprofile"] = { "zprofile", "shell" }, + ["zshenv"] = { "zshenv", "shell" }, + ["zshrc"] = { "zshrc", "shell" }, + addressbook = { "abook", "abook" }, + ahk = { "Autohotkey", "autohotkey" }, + applescript = { "Applescript", "applescript" }, + bash = { "Bash script", "shell" }, + bib = { "BibTeX", "latex" }, + c = { "C ", "c" }, + cabal = { "Cabal file", "haskell" }, + cc = { "C++", "c_plus_plus" }, + cf = { "Configuration file", "config" }, + cfg = { "Configuration file", "config" }, + cl = { "Common Lisp", "lisp" }, + clj = { "Clojure", "clojure" }, + cljs = { "ClojureScript", "clojurescript" }, + cls = { "Visual Basic class module", "visual_basic" }, + cnf = { "Configuration file", "config" }, + coffee = { "CoffeeScript", "coffeescript" }, + conf = { "Configuration file", "config" }, + config = { "Configuration file", "config" }, + cpp = { "C++", "c_plus_plus" }, + cr = { "Crystal", "crystal" }, + cs = { "C#", "c_sharp" }, + css = { "CSS", "css" }, + cxx = { "C++", "c_plus_plus" }, + d = { "D", "d" }, + dart = { "Dart", "dart" }, + dll = { "DLL file", "visual_basic" }, + e = { "Eiffel", "eiffel" }, + elm = { "Elm", "elm" }, + erl = { "Erlang", "erlang" }, + ex = { "Elixir", "elixir" }, + expect = { "Expect", "tcl" }, + fasl = { "Common Lisp", "lisp" }, + fish = { "Fish script", "fish" }, + fnl = { "Fennel", "fennel" }, + fs = { "F#", "f_sharp" }, + g = { "ANTLR grammar", "antlr" }, + g3 = { "ANTLR 3 grammar", "antlr" }, + g4 = { "ANTLR 4 grammar", "antlr" }, + gemspec = { "Gem Spec", "ruby" }, + go = { "Go", "go" }, + gql = { "GraphQL", "graphql" }, + graphql = { "GraphQL", "graphql" }, + groovy = { "Groovy", "groovy" }, + gsh = { "Groovy", "groovy" }, + gvy = { "Groovy", "groovy" }, + gy = { "Groovy", "groovy" }, + h = { "C header file", "c" }, + hack = { "Hack", "hack" }, + haml = { "Haml", "haml" }, + hpp = { "C++ header file", "c_plus_plus" }, + hs = { "Haskell", "haskell" }, + html = { "HTML", "html" }, + hx = { "Haxe", "haxe" }, + hxx = { "C++ header file", "c_plus_plus" }, + idr = { "Idris", "idris" }, + ini = { "Configuration file", "config" }, + ino = { "Arduino", "arduino" }, + ipynb = { "Jupyter Notebook", "jupyter" }, + java = { "Java", "java" }, + jl = { "Julia", "julia" }, + js = { "JavaScript", "javascript" }, + json = { "JSON", "json" }, + jsx = { "React", "react" }, + ksh = { "KornShell script", "shell" }, + kshrc = { "KornShell config", "shell" }, + kt = { "Kotlin", "kotlin" }, + kv = { "Kivy", "kivy" }, + l = { "Common Lisp", "lisp" }, + less = { "Less", "less" }, + lidr = { "Idris", "idris" }, + liquid = { "Liquid", "liquid" }, + lisp = { "Common Lisp", "lisp" }, + log = { "Log file", "code" }, + lsp = { "Common Lisp", "lisp" }, + lua = { "Lua", "lua" }, + m = { "MATLAB", "matlab" }, + markdown = { "Markdown", "markdown" }, + mat = { "MATLAB", "matlab" }, + md = { "Markdown", "markdown" }, + mdx = { "MDX", "mdx" }, + mjs = { "JavaScript", "javascript" }, + ml = { "OCaml", "ocaml" }, + nim = { "Nim", "nim" }, + nix = { "Nix", "nix" }, + norg = { "Neorg", "neorg" }, + org = { "Org", "org" }, + pb = { "Protobuf", "protobuf" }, + pcss = { "PostCSS", "postcss" }, + pgsql = { "PostgreSQL", "pgsql" }, + php = { "PHP", "php" }, + pl = { "Perl", "perl" }, + plist = { "Property List", "markup" }, + postcss = { "PostCSS", "postcss" }, + proto = { "Protobuf", "protobuf" }, + ps1 = { "PowerShell", "powershell" }, + psd1 = { "PowerShell", "powershell" }, + psm1 = { "PowerShell", "powershell" }, + purs = { "PureScript", "purescript" }, + py = { "Python", "python" }, + r = { "R", "r" }, + raku = { "Raku", "raku" }, + rakudoc = { "Raku", "raku" }, + rakumod = { "Raku", "raku" }, + rakutest = { "Raku", "raku" }, + rb = { "Ruby", "ruby" }, + re = { "Reason", "reason" }, + res = { "ReScript", "rescript" }, + rkt = { "Racket", "racket" }, + rs = { "Rust", "rust" }, + sass = { "Sass", "sass" }, + scala = { "Scala", "scala" }, + scm = { "Scheme", "scheme" }, + scss = { "Sass", "scss" }, + sh = { "Shell script", "shell" }, + shrc = { "Shell config", "shell" }, + snap = { "Jest Snapshot", "jest" }, + sql = { "SQL", "database" }, + ss = { "Scheme", "scheme" }, + svelte = { "Svelte", "svelte" }, + svg = { "SVG", "markup" }, + swift = { "Swift", "swift" }, + tcl = { "Tcl", "tcl" }, + tex = { "LaTeX", "latex" }, + text = { "Text file", "text" }, + tf = { "Terraform", "terraform" }, + tk = { "Tcl/Tk", "tcl" }, + tl = { "Teal", "teal" }, + toml = { "TOML", "toml" }, + ts = { "TypeScript", "typescript" }, + tsx = { "React", "react" }, + txt = { "Text file", "text" }, + uc = { "UnrealScript", "unreal" }, + v = { "Vlang", "vlang" }, + vsh = { "Vlang shell script", "vlang" }, + vb = { "Visual Basic", "visual_basic" }, + vbp = { "Visual Basic project file", "visual_basic" }, + vim = { "Vim", "vim" }, + viml = { "Vim", "vim" }, + vue = { "Vue", "vue" }, + wasm = { "WebAssembly", "webassembly" }, + wast = { "WebAssembly", "webassembly" }, + wat = { "WebAssembly", "webassembly" }, + xml = { "XML", "markup" }, + xsd = { "XML Schema", "markup" }, + xslt = { "XSLT", "markup" }, + yaml = { "YAML", "yaml" }, + yml = { "YAML", "yaml" }, + zig = { "Zig", "zig" }, + zsh = { "Zsh script", "shell" }, + zu = { "Zimbu", "zimbu" }, } diff --git a/lua/presence/file_explorers.lua b/lua/presence/file_explorers.lua index 050d8e7..5d33273 100644 --- a/lua/presence/file_explorers.lua +++ b/lua/presence/file_explorers.lua @@ -1,10 +1,10 @@ -- Different neovim file explorer names keyed by filetype or buffer name return { - ["NvimTree"] = "NvimTree", - ["NERD_tree_"] = "NERDTree", - ["[defx] default-"] = "Defx", - ["netrw"] = "Netrw", - ["TelescopePrompt"] = "Telescope", - ['neo-tree'] = 'Neotree', - ['fern'] = 'Fern' + ["NvimTree"] = "NvimTree", + ["NERD_tree_"] = "NERDTree", + ["[defx] default-"] = "Defx", + ["netrw"] = "Netrw", + ["TelescopePrompt"] = "Telescope", + ["neo-tree"] = "Neotree", + ["fern"] = "Fern", } diff --git a/lua/presence/init.lua b/lua/presence/init.lua index d3dddac..c913242 100644 --- a/lua/presence/init.lua +++ b/lua/presence/init.lua @@ -69,928 +69,929 @@ local plugin_managers = require("presence.plugin_managers") local Discord = require("presence.discord") function Presence:setup(...) - -- Support setup invocation via both dot and colon syntax. - -- To maintain backwards compatibility, colon syntax will still - -- be supported, but dot syntax should be recommended. - local args = { ... } - local options = args[1] - if #args == 0 then - options = self - self = Presence - end + -- Support setup invocation via both dot and colon syntax. + -- To maintain backwards compatibility, colon syntax will still + -- be supported, but dot syntax should be recommended. + local args = { ... } + local options = args[1] + if #args == 0 then + options = self + self = Presence + end - options = options or {} - self.options = options + options = options or {} + self.options = options - -- Initialize logger - self:set_option("log_level", nil, false) - self.log = log:init({ level = options.log_level }) + -- Initialize logger + self:set_option("log_level", nil, false) + self.log = log:init({ level = options.log_level }) - -- 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:lower():find("microsoft") ~= nil, - path_separator = separator, - } + -- 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:lower():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", vim.inspect(vim.loop.os_uname()))) - end + -- 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", vim.inspect(vim.loop.os_uname()))) + end - -- Use the default or user-defined client id if provided - if options.client_id then - self.log:info("Using user-defined Discord client id") - end + -- Use the default or user-defined client id if provided + if options.client_id then + self.log:info("Using user-defined Discord client id") + end - -- General options - self:set_option("auto_update", 1) - self:set_option("client_id", "793271441293967371") - self:set_option("debounce_timeout", 10) - self:set_option("neovim_image_text", "The One True Text Editor") - self:set_option("main_image", "neovim") - self:set_option("enable_line_number", false) - -- Status text options - self:set_option("editing_text", "Editing %s") - self:set_option("file_explorer_text", "Browsing %s") - self:set_option("git_commit_text", "Committing changes") - self:set_option("plugin_manager_text", "Managing plugins") - self:set_option("reading_text", "Reading %s") - self:set_option("workspace_text", "Working on %s") - self:set_option("line_number_text", "Line %s out of %s") - self:set_option("blacklist", {}) - self:set_option("buttons", true) - self:set_option("show_time", true) - -- File assets options - self:set_option("file_assets", {}) - for name, asset in pairs(default_file_assets) do - if not self.options.file_assets[name] then - self.options.file_assets[name] = asset - end - end + -- General options + self:set_option("auto_update", 1) + self:set_option("client_id", "1172122807501594644") + self:set_option("debounce_timeout", 10) + self:set_option("neovim_image_text", "The One True Text Editor") + self:set_option("main_image", "neovim") + self:set_option("enable_line_number", false) + -- Status text options + self:set_option("editing_text", "Editing %s") + self:set_option("file_explorer_text", "Browsing %s") + self:set_option("git_commit_text", "Committing changes") + self:set_option("plugin_manager_text", "Managing plugins") + self:set_option("reading_text", "Reading %s") + self:set_option("workspace_text", "Working on %s") + self:set_option("line_number_text", "Line %s out of %s") + self:set_option("blacklist", {}) + self:set_option("buttons", true) + self:set_option("show_time", true) + -- File assets options + self:set_option("file_assets", {}) + for name, asset in pairs(default_file_assets) do + if not self.options.file_assets[name] then + self.options.file_assets[name] = asset + end + end - -- 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 + -- 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_path, - }) + -- Initialize discord RPC client + self.discord = Discord:init({ + logger = self.log, + client_id = options.client_id, + ipc_socket = discord_socket_path, + }) - -- 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()) - self.log:debug(string.format("Using id %s", self.id)) + -- 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()) + self.log:debug(string.format("Using id %s", self.id)) - -- Ensure auto-update config is reflected in its global var setting - vim.api.nvim_set_var("presence_auto_update", options.auto_update) + -- Ensure auto-update config is reflected in its global var setting + vim.api.nvim_set_var("presence_auto_update", options.auto_update) - -- Set autocommands - vim.fn["presence#SetAutoCmds"]() + -- Set autocommands + vim.fn["presence#SetAutoCmds"]() - self.log:info("Completed plugin setup") + self.log:info("Completed plugin setup") - -- Set global variable to indicate plugin has been set up - vim.api.nvim_set_var("presence_has_setup", 1) + -- Set global variable to indicate plugin has been set up + vim.api.nvim_set_var("presence_has_setup", 1) - -- Register self to any remote Neovim instances - self:register_self() + -- Register self to any remote Neovim instances + self:register_self() - return self + return self end -- Normalize the OS name from uname function Presence.get_os_name(uname) - if uname.sysname:find("Windows") or uname.sysname:find("MINGW") then - return "windows" - elseif uname.sysname:find("Darwin") then - return "macos" - elseif uname.sysname:find("Linux") then - return "linux" - end + if uname.sysname:find("Windows") or uname.sysname:find("MINGW") then + return "windows" + elseif uname.sysname:find("Darwin") then + return "macos" + elseif uname.sysname:find("Linux") then + return "linux" + end - return "unknown" + 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 - return value and 1 or 0 - end + if type(value) == "boolean" then + return value and 1 or 0 + end - return value + return value end -- Set option using either vim global or setup table function Presence:set_option(option, default, validate) - default = self.coalesce_option(default) - validate = validate == nil and true or validate + default = self.coalesce_option(default) + validate = validate == nil and true or validate - local g_variable = string.format("presence_%s", option) + local g_variable = string.format("presence_%s", option) - self.options[option] = self.coalesce_option(self.options[option]) + self.options[option] = self.coalesce_option(self.options[option]) - if validate then - -- Warn on any duplicate user-defined options - self:check_dup_options(option) - end + if validate then + -- Warn on any duplicate user-defined options + self:check_dup_options(option) + end - self.options[option] = self.options[option] or vim.g[g_variable] or default + self.options[option] = self.options[option] or vim.g[g_variable] or default end -- Check and warn for duplicate user-defined options function Presence:check_dup_options(option) - local g_variable = string.format("presence_%s", option) + local g_variable = string.format("presence_%s", option) - if self.options[option] ~= nil and vim.g[g_variable] ~= nil then - local warning_fmt = "Duplicate options: `g:%s` and setup option `%s`" - local warning_msg = string.format(warning_fmt, g_variable, option) + if self.options[option] ~= nil and vim.g[g_variable] ~= nil then + local warning_fmt = "Duplicate options: `g:%s` and setup option `%s`" + local warning_msg = string.format(warning_fmt, g_variable, option) - self.log:warn(warning_msg) - end + self.log:warn(warning_msg) + 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)) + 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 + -- 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 + 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("Checked Discord IPC socket, looks good!") - end) + self.log:debug("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...") + self.log:debug("Canceling Discord presence...") - if not self.discord:is_connected() then - return - end + if not self.discord:is_connected() then + return + end - self.discord:set_activity(nil, function(err) - if err then - self.log:error(string.format("Failed to cancel activity in Discord: %s", err)) - return - end + self.discord:set_activity(nil, function(err) + if err then + self.log:error(string.format("Failed to cancel activity in Discord: %s", err)) + return + end - self.log:info("Canceled Discord presence") - end) + self.log:info("Canceled Discord presence") + end) end -- Call a command on a remote Neovim instance at the provided IPC path function Presence:call_remote_nvim_instance(socket, command) - local remote_nvim_instance = vim.loop.new_pipe(true) + local remote_nvim_instance = vim.loop.new_pipe(true) - remote_nvim_instance:connect(socket, function() - self.log:debug(string.format("Connected to remote nvim instance at %s", socket)) + remote_nvim_instance:connect(socket, function() + self.log:debug(string.format("Connected to remote nvim instance at %s", socket)) - local packed = msgpack.pack({ 0, 0, "nvim_command", { command } }) + local packed = msgpack.pack({ 0, 0, "nvim_command", { command } }) - remote_nvim_instance:write(packed, function() - self.log:debug(string.format("Wrote to remote nvim instance: %s", socket)) - end) - end) + remote_nvim_instance:write(packed, function() + self.log:debug(string.format("Wrote to remote nvim instance: %s", socket)) + end) + end) end -- Call a Presence method on a remote instance with a given list of arguments function Presence:call_remote_method(socket, name, args) - local command_fmt = "lua package.loaded.presence:%s(%s)" + local command_fmt = "lua package.loaded.presence:%s(%s)" - -- Stringify the list of args - for i = 1, #args do - local arg = args[i] - if type(arg) == "string" then - args[i] = string.format([["%s"]], arg) - elseif type(arg) == "boolean" then - args[i] = string.format([["%s"]], tostring(arg)) - elseif type(arg) == "table" then - -- Wrap serpent dump with function invocation to pass in the table value - args[i] = string.format("(function() %s end)()", serpent.dump(arg)) - end - end + -- Stringify the list of args + for i = 1, #args do + local arg = args[i] + if type(arg) == "string" then + args[i] = string.format([["%s"]], arg) + elseif type(arg) == "boolean" then + args[i] = string.format([["%s"]], tostring(arg)) + elseif type(arg) == "table" then + -- Wrap serpent dump with function invocation to pass in the table value + args[i] = string.format("(function() %s end)()", serpent.dump(arg)) + end + end - local arglist = table.concat(args or {}, ",") - local command = string.format(command_fmt, name, arglist) - self:call_remote_nvim_instance(socket, command) + local arglist = table.concat(args or {}, ",") + local command = string.format(command_fmt, name, arglist) + self:call_remote_nvim_instance(socket, command) end function Presence:connect(on_done) - self.log:debug("Connecting to Discord...") + self.log:debug("Connecting to Discord...") - self.is_connecting = true + self.is_connecting = true - self.discord:connect(function(err) - self.is_connecting = false + self.discord:connect(function(err) + self.is_connecting = false - -- Handle known connection errors - if err == "EISCONN" then - self.log:info("Already connected to Discord") - elseif err == "ECONNREFUSED" then - self.log:warn("Failed to connect to Discord: " .. err .. " (is Discord running?)") - return - elseif err then - self.log:error("Failed to connect to Discord: " .. err) - return - end + -- Handle known connection errors + if err == "EISCONN" then + self.log:info("Already connected to Discord") + elseif err == "ECONNREFUSED" then + self.log:warn("Failed to connect to Discord: " .. err .. " (is Discord running?)") + return + elseif err then + self.log:error("Failed to connect to Discord: " .. err) + return + end - self.log:info("Connected to Discord") - self.is_connected = true + self.log:info("Connected to Discord") + self.is_connected = true - if on_done then - on_done() - end - end) + if on_done then + on_done() + end + end) end function Presence:authorize(on_done) - self.log:debug("Authorizing with Discord...") + self.log:debug("Authorizing with Discord...") - -- Track authorization state to avoid race conditions - -- (Discord rejects when multiple auth requests are sent at once) - self.is_authorizing = true + -- Track authorization state to avoid race conditions + -- (Discord rejects when multiple auth requests are sent at once) + self.is_authorizing = true - self.discord:authorize(function(err, response) - self.is_authorizing = false + self.discord:authorize(function(err, response) + self.is_authorizing = false - if err and err:find(".*already did handshake.*") then - self.log:info("Already authorized with Discord") - self.is_authorized = true - return on_done() - elseif err then - self.log:error("Failed to authorize with Discord: " .. err) - self.is_authorized = false - return - end + if err and err:find(".*already did handshake.*") then + self.log:info("Already authorized with Discord") + self.is_authorized = true + return on_done() + elseif err then + self.log:error("Failed to authorize with Discord: " .. err) + self.is_authorized = false + return + end - if not response then - self.log:info(string.format("Authorized with Discord for %s", "Response was nil")) - else - self.log:info(string.format("Authorized with Discord for %s", response.data.user.username)) - end + if not response then + self.log:info(string.format("Authorized with Discord for %s", "Response was nil")) + else + self.log:info(string.format("Authorized with Discord for %s", response.data.user.username)) + end - self.is_authorized = true + self.is_authorized = true - if on_done then - on_done() - end - end) + if on_done then + on_done() + end + end) end -- Find the Discord socket from temp runtime directories function Presence:get_discord_socket_path() - local sock_name = "discord-ipc-0" - local sock_path = nil + local sock_name = "discord-ipc-0" + local sock_path = nil - 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") + 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") - if path then - sock_path = path:match("/$") and path .. sock_name or path .. "/" .. sock_name - end - elseif self.os.name == "linux" then - -- Check various temp directory environment variables - local env_vars = { - "XDG_RUNTIME_DIR", - "TEMP", - "TMP", - "TMPDIR", - } + if path then + sock_path = path:match("/$") and path .. sock_name or path .. "/" .. sock_name + end + elseif self.os.name == "linux" then + -- Check various temp directory environment variables + local env_vars = { + "XDG_RUNTIME_DIR", + "TEMP", + "TMP", + "TMPDIR", + } - local xdg_path = os.getenv("XDG_RUNTIME_DIR") - if xdg_path then - -- Append app/com.discordapp.Discord/ to the end of the path (make sure that / is at the end of xdg_path - -- before appending) - xdg_path = xdg_path and xdg_path:match("/$") and xdg_path .. "app/com.discordapp.Discord" - or xdg_path .. "/app/com.discordapp.Discord" - self.log:debug(string.format("Using XDG runtime path: %s", xdg_path)) - sock_path = xdg_path:match("/$") and xdg_path .. sock_name or xdg_path .. "/" .. sock_name - -- Check if the socket path exists and if not set it to nil - sock_path = vim.fn.filereadable(sock_path) == 1 and sock_path or nil - end + local xdg_path = os.getenv("XDG_RUNTIME_DIR") + if xdg_path then + -- Append app/com.discordapp.Discord/ to the end of the path (make sure that / is at the end of xdg_path + -- before appending) + xdg_path = xdg_path and xdg_path:match("/$") and xdg_path .. "app/com.discordapp.Discord" + or xdg_path .. "/app/com.discordapp.Discord" + self.log:debug(string.format("Using XDG runtime path: %s", xdg_path)) + sock_path = xdg_path:match("/$") and xdg_path .. sock_name or xdg_path .. "/" .. sock_name + -- Check if the socket path exists and if not set it to nil + sock_path = vim.fn.filereadable(sock_path) == 1 and sock_path or nil + end - -- If the socket path is still nil, check other temp directories - if not sock_path then - 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 - end + -- If the socket path is still nil, check other temp directories + if not sock_path then + 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 + end - return sock_path + return sock_path end -- Gets the file path of the current vim buffer function Presence.get_current_buffer() - local current_buffer = vim.api.nvim_get_current_buf() - return vim.api.nvim_buf_get_name(current_buffer) + local current_buffer = vim.api.nvim_get_current_buf() + return vim.api.nvim_buf_get_name(current_buffer) end -- Gets the current project name function Presence:get_project_name(file_path) - if not file_path then - return nil - end + if not file_path then + return nil + end - -- Escape quotes in the file path - file_path = file_path:gsub([["]], [[\"]]) + -- Escape quotes in the file path + file_path = file_path:gsub([["]], [[\"]]) - -- TODO: Only checks for a git repository, could add more checks here - -- Might want to run this in a background process depending on performance - local project_path_cmd = "git rev-parse --show-toplevel" - project_path_cmd = file_path and string.format([[cd "%s" && %s]], file_path, project_path_cmd) or project_path_cmd + -- TODO: Only checks for a git repository, could add more checks here + -- Might want to run this in a background process depending on performance + local project_path_cmd = "git rev-parse --show-toplevel" + project_path_cmd = file_path and string.format([[cd "%s" && %s]], file_path, project_path_cmd) or project_path_cmd - local project_path = vim.fn.system(project_path_cmd) - project_path = vim.trim(project_path) + local project_path = vim.fn.system(project_path_cmd) + project_path = vim.trim(project_path) - if project_path:find("fatal.*") then - self.log:info("Not a git repository, skipping...") - return nil - end - if vim.v.shell_error ~= 0 or #project_path == 0 then - local message_fmt = "Failed to get project name (error code %d): %s" - self.log:error(string.format(message_fmt, vim.v.shell_error, project_path)) - return nil - end + if project_path:find("fatal.*") then + self.log:info("Not a git repository, skipping...") + return nil + end + if vim.v.shell_error ~= 0 or #project_path == 0 then + local message_fmt = "Failed to get project name (error code %d): %s" + self.log:error(string.format(message_fmt, vim.v.shell_error, project_path)) + return nil + end - -- Since git always uses forward slashes, replace with backslash in Windows - if self.os.name == "windows" then - project_path = project_path:gsub("/", [[\]]) - end + -- Since git always uses forward slashes, replace with backslash in Windows + if self.os.name == "windows" then + project_path = project_path:gsub("/", [[\]]) + end - return self.get_filename(project_path, self.os.path_separator), project_path + return self.get_filename(project_path, self.os.path_separator), project_path end -- Get the name of the parent directory for the given path function Presence.get_dir_path(path, path_separator) - return path:match(string.format("^(.+%s.+)%s.*$", path_separator, path_separator)) + return path:match(string.format("^(.+%s.+)%s.*$", path_separator, path_separator)) end -- Get the name of the file for the given path function Presence.get_filename(path, path_separator) - return path:match(string.format("^.+%s(.+)$", path_separator)) + return path:match(string.format("^.+%s(.+)$", path_separator)) end -- Get the file extension for the given filename function Presence.get_file_extension(path) - return path:match("^.+%.(.+)$") + return path:match("^.+%.(.+)$") end -- Format any status text via options and support custom formatter functions function Presence:format_status_text(status_type, ...) - local option_name = string.format("%s_text", status_type) - local text_option = self.options[option_name] - if type(text_option) == "function" then - return text_option(...) - else - return string.format(text_option, ...) - end + local option_name = string.format("%s_text", status_type) + local text_option = self.options[option_name] + if type(text_option) == "function" then + return text_option(...) + else + return string.format(text_option, ...) + end end -- Get the status text for the current buffer function Presence:get_status_text(filename) - local file_explorer = file_explorers[vim.bo.filetype:match("[^%d]+")] - or file_explorers[(filename or ""):match("[^%d]+")] - local plugin_manager = plugin_managers[vim.bo.filetype] + local file_explorer = file_explorers[vim.bo.filetype:match("[^%d]+")] + or file_explorers[(filename or ""):match("[^%d]+")] + local plugin_manager = plugin_managers[vim.bo.filetype] - if file_explorer then - return self:format_status_text("file_explorer", file_explorer) - elseif plugin_manager then - return self:format_status_text("plugin_manager", plugin_manager) - end + if file_explorer then + return self:format_status_text("file_explorer", file_explorer) + elseif plugin_manager then + return self:format_status_text("plugin_manager", plugin_manager) + end - if not filename or filename == "" then - return nil - end + if not filename or filename == "" then + return nil + end - if vim.bo.modifiable and not vim.bo.readonly then - if vim.bo.filetype == "gitcommit" then - return self:format_status_text("git_commit", filename) - elseif filename then - return self:format_status_text("editing", filename) - end - elseif filename then - return self:format_status_text("reading", filename) - end + if vim.bo.modifiable and not vim.bo.readonly then + if vim.bo.filetype == "gitcommit" then + return self:format_status_text("git_commit", filename) + elseif filename then + return self:format_status_text("editing", filename) + end + elseif filename then + return self:format_status_text("reading", filename) + end end -- 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 + self.log:debug("Getting nvim socket paths...") + local sockets = {} + local parser = {} + local cmd - 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") + 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']], - } - 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 + cmd = { + "sh", + "-c", + shell_cmd, + } + elseif self.os.name == "windows" then + cmd = { + "powershell.exe", + "-Command", + [[(Get-ChildItem \\.\pipe\).FullName | findstr 'nvim']], + } + 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 + -- 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("ss") == 1 then - -- Use `ss` if available - cmd = table.concat({ - "ss -lx", - [[grep "nvim.*/0"]], - }, "|") + cmd = table.concat({ + "netstat -u", + [[grep --color=never "nvim.*/0"]], + }, "|") + elseif self.os.name == "linux" then + if 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 - elseif vim.fn.executable("netstat") == 1 then - -- Use `netstat` if available - cmd = table.concat({ - "netstat -u", - [[grep --color=never "nvim.*/0"]], - }, "|") + -- Define ss output parser + function parser.parse(data) + return data:match("%s(/.-)%s") + end + elseif 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 - 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 + -- Define netstat output parser + function parser.parse(data) + return data:match("%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 function handle_data(_, data) - if not data then - return - end + local function handle_data(_, data) + if not data then + return + end - for i = 1, #data do - 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 - end + for i = 1, #data do + 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 + end - local function handle_error(_, data) - if not data then - return - end + local function handle_error(_, data) + if not data then + return + end - if data[1] ~= "" then - self.log:error(string.format("Unable to get nvim socket paths: %s", data[1])) - end - end + if data[1] ~= "" then + 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 paths: %s", vim.inspect(sockets))) - on_done(sockets) - end + local function handle_exit() + 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, - on_exit = handle_exit, - }) + 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, + on_exit = handle_exit, + }) end -- Wrap calls to Discord that require prior connection and authorization function Presence.discord_event(on_ready) - return function(self, ...) - if not self.discord.ipc_socket then - self.log:debug("Discord IPC socket not found, skipping...") - return - end + return function(self, ...) + if not self.discord.ipc_socket then + self.log:debug("Discord IPC socket not found, skipping...") + return + end - local args = { ... } - local callback = function() - on_ready(self, unpack(args)) - end + local args = { ... } + local callback = function() + on_ready(self, unpack(args)) + end - -- Call Discord if already connected and authorized - if self.is_connected and self.is_authorized then - return callback() - end + -- Call Discord if already connected and authorized + if self.is_connected and self.is_authorized then + return callback() + end - -- Schedule event if currently authorizing with Discord - if self.is_connecting or self.is_authorizing then - local action = self.is_connecting and "connecting" or "authorizing" - local message_fmt = "Currently %s with Discord, scheduling callback for later..." - self.log:debug(string.format(message_fmt, action)) - return vim.schedule(callback) - end + -- Schedule event if currently authorizing with Discord + if self.is_connecting or self.is_authorizing then + local action = self.is_connecting and "connecting" or "authorizing" + local message_fmt = "Currently %s with Discord, scheduling callback for later..." + self.log:debug(string.format(message_fmt, action)) + return vim.schedule(callback) + end - -- Authorize if connected but not yet authorized yet - if self.is_connected and not self.is_authorized then - return self:authorize(callback) - end + -- Authorize if connected but not yet authorized yet + if self.is_connected and not self.is_authorized then + return self:authorize(callback) + end - -- Connect and authorize plugin with Discord - self:connect(function() - if self.is_authorized then - return callback() - end + -- Connect and authorize plugin with Discord + self:connect(function() + if self.is_authorized then + return callback() + end - self:authorize(callback) - end) - end + self:authorize(callback) + end) + end end -- Check if the current project/parent is in blacklist function Presence:check_blacklist(buffer, parent_dirpath, project_dirpath) - local parent_dirname = nil - local project_dirname = nil + local parent_dirname = nil + local project_dirname = nil - -- Parse parent/project directory name - if parent_dirpath then - parent_dirname = self.get_filename(parent_dirpath, self.os.path_separator) - end + -- Parse parent/project directory name + if parent_dirpath then + parent_dirname = self.get_filename(parent_dirpath, self.os.path_separator) + end - if project_dirpath then - project_dirname = self.get_filename(project_dirpath, self.os.path_separator) - end + if project_dirpath then + project_dirname = self.get_filename(project_dirpath, self.os.path_separator) + end - -- Blacklist table - local blacklist_table = self.options["blacklist"] + -- Blacklist table + local blacklist_table = self.options["blacklist"] - -- Loop over the values to see if the provided project/path is in the blacklist - for _, val in pairs(blacklist_table) do - -- Matches buffer exactly - if buffer:match(val) == buffer then - return true - end - -- Match parent either by Lua pattern or by plain string - local is_parent_directory_blacklisted = parent_dirpath - and ( - (parent_dirpath:match(val) == parent_dirpath or parent_dirname:match(val) == parent_dirname) - or (parent_dirpath:find(val, nil, true) or parent_dirname:find(val, nil, true)) - ) - if is_parent_directory_blacklisted then - return true - end - -- Match project either by Lua pattern or by plain string - local is_project_directory_blacklisted = project_dirpath - and ( - (project_dirpath:match(val) == project_dirpath or project_dirname:match(val) == project_dirname) - or (project_dirpath:find(val, nil, true) or project_dirname:find(val, nil, true)) - ) - if is_project_directory_blacklisted then - return true - end - end + -- Loop over the values to see if the provided project/path is in the blacklist + for _, val in pairs(blacklist_table) do + -- Matches buffer exactly + if buffer:match(val) == buffer then + return true + end + -- Match parent either by Lua pattern or by plain string + local is_parent_directory_blacklisted = parent_dirpath + and ( + (parent_dirpath:match(val) == parent_dirpath or parent_dirname:match(val) == parent_dirname) + or (parent_dirpath:find(val, nil, true) or parent_dirname:find(val, nil, true)) + ) + if is_parent_directory_blacklisted then + return true + end + -- Match project either by Lua pattern or by plain string + local is_project_directory_blacklisted = project_dirpath + and ( + (project_dirpath:match(val) == project_dirpath or project_dirname:match(val) == project_dirname) + or (project_dirpath:find(val, nil, true) or project_dirname:find(val, nil, true)) + ) + if is_project_directory_blacklisted then + return true + end + end - return false + return false end -- Get either user-configured buttons or the create default "View Repository" button definition function Presence:get_buttons(buffer, parent_dirpath) - -- User configured a static buttons table - if type(self.options.buttons) == "table" then - local is_plural = #self.options.buttons > 1 - local s = is_plural and "s" or "" - self.log:debug(string.format("Using custom-defined button%s", s)) + -- User configured a static buttons table + if type(self.options.buttons) == "table" then + local is_plural = #self.options.buttons > 1 + local s = is_plural and "s" or "" + self.log:debug(string.format("Using custom-defined button%s", s)) - return self.options.buttons - end + return self.options.buttons + end - -- Retrieve the git repository URL - local repo_url - if parent_dirpath then - -- Escape quotes in the file path - local path = parent_dirpath:gsub([["]], [[\"]]) - local git_url_cmd = "git config --get remote.origin.url" - local cmd = path and string.format([[cd "%s" && %s]], path, git_url_cmd) or git_url_cmd + -- Retrieve the git repository URL + local repo_url + if parent_dirpath then + -- Escape quotes in the file path + local path = parent_dirpath:gsub([["]], [[\"]]) + local git_url_cmd = "git config --get remote.origin.url" + local cmd = path and string.format([[cd "%s" && %s]], path, git_url_cmd) or git_url_cmd - -- Trim and coerce empty string value to null - repo_url = vim.trim(vim.fn.system(cmd)) - repo_url = repo_url ~= "" and repo_url or nil - end + -- Trim and coerce empty string value to null + repo_url = vim.trim(vim.fn.system(cmd)) + repo_url = repo_url ~= "" and repo_url or nil + end - -- User configured a function to dynamically create buttons table - if type(self.options.buttons) == "function" then - self.log:debug("Using custom-defined button config function") - return self.options.buttons(buffer, repo_url) - end + -- User configured a function to dynamically create buttons table + if type(self.options.buttons) == "function" then + self.log:debug("Using custom-defined button config function") + return self.options.buttons(buffer, repo_url) + end - -- Default behavior to show a "View Repository" button if the repo URL is valid - if repo_url then - -- Check if repo url uses short ssh syntax - local domain, project = repo_url:match("^git@(.+):(.+)$") - if domain and project then - self.log:debug(string.format("Repository URL uses short ssh syntax: %s", repo_url)) - repo_url = string.format("https://%s/%s", domain, project) - end + -- Default behavior to show a "View Repository" button if the repo URL is valid + if repo_url then + -- Check if repo url uses short ssh syntax + local domain, project = repo_url:match("^git@(.+):(.+)$") + if domain and project then + self.log:debug(string.format("Repository URL uses short ssh syntax: %s", repo_url)) + repo_url = string.format("https://%s/%s", domain, project) + end - -- Check if repo url uses a valid protocol - local protocols = { - "ftp", - "git", - "http", - "https", - "ssh", - } - local protocol, relative = repo_url:match("^(.+)://(.+)$") - if not vim.tbl_contains(protocols, protocol) or not relative then - self.log:debug(string.format("Repository URL uses invalid protocol: %s", repo_url)) - return nil - end + -- Check if repo url uses a valid protocol + local protocols = { + "ftp", + "git", + "http", + "https", + "ssh", + } + local protocol, relative = repo_url:match("^(.+)://(.+)$") + if not vim.tbl_contains(protocols, protocol) or not relative then + self.log:debug(string.format("Repository URL uses invalid protocol: %s", repo_url)) + return nil + end - -- Check if repo url has the user specified - local user, path = relative:match("^(.+)@(.+)$") - if user and path then - self.log:debug(string.format("Repository URL has user specified: %s", repo_url)) - repo_url = string.format("https://%s", path) - else - repo_url = string.format("https://%s", relative) - end + -- Check if repo url has the user specified + local user, path = relative:match("^(.+)@(.+)$") + if user and path then + self.log:debug(string.format("Repository URL has user specified: %s", repo_url)) + repo_url = string.format("https://%s", path) + else + repo_url = string.format("https://%s", relative) + end - self.log:debug(string.format("Adding button with repository URL: %s", repo_url)) + self.log:debug(string.format("Adding button with repository URL: %s", repo_url)) - return { - { label = "View Repository", url = repo_url }, - } - end + return { + { label = "View Repository", url = repo_url }, + } + end - return nil + return nil end -- Update Rich Presence for the provided vim buffer function Presence:update_for_buffer(buffer, should_debounce) - -- Avoid unnecessary updates if the previous activity was for the current buffer - -- (allow same-buffer updates when line numbers are enabled) - if self.options.enable_line_number == 0 and self.last_activity.file == buffer then - self.log:debug(string.format("Activity already set for %s, skipping...", buffer)) - return - end + -- Avoid unnecessary updates if the previous activity was for the current buffer + -- (allow same-buffer updates when line numbers are enabled) + if self.options.enable_line_number == 0 and self.last_activity.file == buffer then + self.log:debug(string.format("Activity already set for %s, skipping...", buffer)) + return + end - -- Parse vim buffer - local filename = self.get_filename(buffer, self.os.path_separator) - local parent_dirpath = self.get_dir_path(buffer, self.os.path_separator) - local extension = filename and self.get_file_extension(filename) or nil - self.log:debug(string.format("Parsed filename %s with %s extension", filename, extension or "no")) + -- Parse vim buffer + local filename = self.get_filename(buffer, self.os.path_separator) + local parent_dirpath = self.get_dir_path(buffer, self.os.path_separator) + local extension = filename and self.get_file_extension(filename) or nil + self.log:debug(string.format("Parsed filename %s with %s extension", filename, extension or "no")) - -- Return early if there is no valid activity status text to set - local status_text = self:get_status_text(filename) - if not status_text then - return self.log:debug("No status text for the given buffer, skipping...") - end + -- Return early if there is no valid activity status text to set + local status_text = self:get_status_text(filename) + if not status_text then + return self.log:debug("No status text for the given buffer, skipping...") + end - -- Get project information - self.log:debug(string.format("Getting project name for %s...", parent_dirpath)) - local project_name, project_path = self:get_project_name(parent_dirpath) + -- Get project information + self.log:debug(string.format("Getting project name for %s...", parent_dirpath)) + local project_name, project_path = self:get_project_name(parent_dirpath) - -- Check for blacklist - local is_blacklisted = #self.options.blacklist > 0 and self:check_blacklist(buffer, parent_dirpath, project_path) - if is_blacklisted then - self.last_activity.file = buffer - self.log:debug("Either project or directory name is blacklisted, skipping...") - self:cancel() - return - end + -- Check for blacklist + local is_blacklisted = #self.options.blacklist > 0 and self:check_blacklist(buffer, parent_dirpath, project_path) + if is_blacklisted then + self.last_activity.file = buffer + self.log:debug("Either project or directory name is blacklisted, skipping...") + self:cancel() + return + end - local activity_set_at = os.time() - -- If we shouldn't debounce and we trigger an activity, keep this value the same. - -- Otherwise set it to the current time. - local relative_activity_set_at = should_debounce and self.last_activity.relative_set_at or os.time() + local activity_set_at = os.time() + -- If we shouldn't debounce and we trigger an activity, keep this value the same. + -- Otherwise set it to the current time. + local relative_activity_set_at = should_debounce and self.last_activity.relative_set_at or os.time() - self.log:debug(string.format("Setting activity for %s...", buffer and #buffer > 0 and buffer or "unnamed buffer")) + self.log:debug(string.format("Setting activity for %s...", buffer and #buffer > 0 and buffer or "unnamed buffer")) - -- Determine image text and asset key - local name = filename - local asset_key = "code" - local description = filename - local file_asset = self.options.file_assets[filename] or self.options.file_assets[extension] - if file_asset then - name, asset_key, description = unpack(file_asset) - self.log:debug(string.format("Using file asset: %s", vim.inspect(file_asset))) - end + -- Determine image text and asset key + local name = filename + local asset_key = "code" + local description = filename + local file_asset = self.options.file_assets[filename] or self.options.file_assets[extension] + if file_asset then + name, asset_key, description = unpack(file_asset) + self.log:debug(string.format("Using file asset: %s", vim.inspect(file_asset))) + end - -- Construct activity asset information - local file_text = description or name - local neovim_image_text = self.options.neovim_image_text - local use_file_as_main_image = self.options.main_image == "file" - local use_neovim_as_main_image = self.options.main_image == "neovim" - local assets = { - large_image = use_file_as_main_image and asset_key or use_neovim_as_main_image - and "neovim" or self.options.main_image, - large_text = use_file_as_main_image and file_text or neovim_image_text, - small_image = use_file_as_main_image and "neovim" or asset_key, - small_text = use_file_as_main_image and neovim_image_text or file_text, - } + -- Construct activity asset information + local file_text = description or name + local neovim_image_text = self.options.neovim_image_text + local use_file_as_main_image = self.options.main_image == "file" + local use_neovim_as_main_image = self.options.main_image == "neovim" + local assets = { + large_image = use_file_as_main_image and asset_key + or use_neovim_as_main_image and "neovim" + or self.options.main_image, + large_text = use_file_as_main_image and file_text or neovim_image_text, + small_image = use_file_as_main_image and "neovim" or asset_key, + small_text = use_file_as_main_image and neovim_image_text or file_text, + } - local activity = { - state = status_text, - assets = assets, - timestamps = self.options.show_time == 1 and { - start = relative_activity_set_at, - } or nil, - } + local activity = { + state = status_text, + assets = assets, + timestamps = self.options.show_time == 1 and { + start = relative_activity_set_at, + } or nil, + } - -- Add button that links to the git workspace remote origin url - if self.options.buttons ~= 0 then - local buttons = self:get_buttons(buffer, parent_dirpath) - if buttons then - self.log:debug(string.format("Attaching buttons to activity: %s", vim.inspect(buttons))) - activity.buttons = buttons - end - end + -- Add button that links to the git workspace remote origin url + if self.options.buttons ~= 0 then + local buttons = self:get_buttons(buffer, parent_dirpath) + if buttons then + self.log:debug(string.format("Attaching buttons to activity: %s", vim.inspect(buttons))) + activity.buttons = buttons + end + end - -- Get the current line number and line count if the user has set the enable_line_number option - if self.options.enable_line_number == 1 then - self.log:debug("Getting line number for current buffer...") + -- Get the current line number and line count if the user has set the enable_line_number option + if self.options.enable_line_number == 1 then + self.log:debug("Getting line number for current buffer...") - local line_number = vim.api.nvim_win_get_cursor(0)[1] - local line_count = vim.api.nvim_buf_line_count(0) - local line_number_text = self:format_status_text("line_number", line_number, line_count) + local line_number = vim.api.nvim_win_get_cursor(0)[1] + local line_count = vim.api.nvim_buf_line_count(0) + local line_number_text = self:format_status_text("line_number", line_number, line_count) - activity.details = line_number_text + activity.details = line_number_text - self.workspace = nil - self.last_activity = { - id = self.id, - file = buffer, - set_at = activity_set_at, - relative_set_at = relative_activity_set_at, - workspace = nil, - } - else - -- Include project details if available and if the user hasn't set the enable_line_number option - if project_name then - self.log:debug(string.format("Detected project: %s", project_name)) + self.workspace = nil + self.last_activity = { + id = self.id, + file = buffer, + set_at = activity_set_at, + relative_set_at = relative_activity_set_at, + workspace = nil, + } + else + -- Include project details if available and if the user hasn't set the enable_line_number option + if project_name then + self.log:debug(string.format("Detected project: %s", project_name)) - activity.details = self:format_status_text("workspace", project_name, buffer) + activity.details = self:format_status_text("workspace", project_name, buffer) - self.workspace = project_path - self.last_activity = { - id = self.id, - file = buffer, - set_at = activity_set_at, - relative_set_at = relative_activity_set_at, - workspace = project_path, - } + self.workspace = project_path + self.last_activity = { + id = self.id, + file = buffer, + set_at = activity_set_at, + relative_set_at = relative_activity_set_at, + workspace = project_path, + } - if self.workspaces[project_path] then - self.workspaces[project_path].updated_at = activity_set_at - activity.timestamps = self.options.show_time == 1 - and { - start = self.workspaces[project_path].started_at, - } - or nil - else - self.workspaces[project_path] = { - started_at = activity_set_at, - updated_at = activity_set_at, - } - end - else - self.log:debug("No project detected") + if self.workspaces[project_path] then + self.workspaces[project_path].updated_at = activity_set_at + activity.timestamps = self.options.show_time == 1 + and { + start = self.workspaces[project_path].started_at, + } + or nil + else + self.workspaces[project_path] = { + started_at = activity_set_at, + updated_at = activity_set_at, + } + end + else + self.log:debug("No project detected") - self.workspace = nil - self.last_activity = { - id = self.id, - file = buffer, - set_at = activity_set_at, - relative_set_at = relative_activity_set_at, - workspace = nil, - } + self.workspace = nil + self.last_activity = { + id = self.id, + file = buffer, + set_at = activity_set_at, + relative_set_at = relative_activity_set_at, + workspace = nil, + } - -- When no project is detected, set custom workspace text if: - -- * The custom function returns custom workspace text - -- * The configured workspace text does not contain a directive - -- (can't use the `format_status_text` method here) - local workspace_text = self.options.workspace_text - if type(workspace_text) == "function" then - local custom_workspace_text = workspace_text(nil, buffer) - if custom_workspace_text then - activity.details = custom_workspace_text - end - elseif not workspace_text:find("%s") then - activity.details = workspace_text - end - end - end + -- When no project is detected, set custom workspace text if: + -- * The custom function returns custom workspace text + -- * The configured workspace text does not contain a directive + -- (can't use the `format_status_text` method here) + local workspace_text = self.options.workspace_text + if type(workspace_text) == "function" then + local custom_workspace_text = workspace_text(nil, buffer) + if custom_workspace_text then + activity.details = custom_workspace_text + end + elseif not workspace_text:find("%s") then + activity.details = workspace_text + end + end + end - -- Sync activity to all peers - self.log:debug("Sync activity to all peers...") - self:sync_self_activity() + -- Sync activity to all peers + self.log:debug("Sync activity to all peers...") + self:sync_self_activity() - self.log:debug("Setting Discord activity...") - self.discord:set_activity(activity, function(err) - if err then - self.log:error(string.format("Failed to set activity in Discord: %s", err)) - return - end + self.log:debug("Setting Discord activity...") + self.discord:set_activity(activity, function(err) + if err then + self.log:error(string.format("Failed to set activity in Discord: %s", err)) + return + end - self.log:info(string.format("Set activity in Discord for %s", filename)) - end) + self.log:info(string.format("Set activity in Discord for %s", filename)) + end) end -- Update Rich Presence for the current or provided vim buffer for an authorized connection Presence.update = Presence.discord_event(function(self, buffer, should_debounce) - -- Default update to not debounce by default - if should_debounce == nil then - should_debounce = false - end + -- Default update to not debounce by default + if should_debounce == nil then + should_debounce = false + end - -- Debounce Rich Presence updates (default to 10 seconds): - -- https://discord.com/developers/docs/rich-presence/how-to#updating-presence - local last_updated_at = self.last_activity.set_at - local debounce_timeout = self.options.debounce_timeout - local should_skip = should_debounce - and debounce_timeout - and last_updated_at - and os.time() - last_updated_at <= debounce_timeout + -- Debounce Rich Presence updates (default to 10 seconds): + -- https://discord.com/developers/docs/rich-presence/how-to#updating-presence + local last_updated_at = self.last_activity.set_at + local debounce_timeout = self.options.debounce_timeout + local should_skip = should_debounce + and debounce_timeout + and last_updated_at + and os.time() - last_updated_at <= debounce_timeout - if should_skip then - local message_fmt = "Last activity sent was within %d seconds ago, skipping..." - self.log:debug(string.format(message_fmt, debounce_timeout)) - return - end + if should_skip then + local message_fmt = "Last activity sent was within %d seconds ago, skipping..." + self.log:debug(string.format(message_fmt, debounce_timeout)) + return + end - if buffer then - self:update_for_buffer(buffer, should_debounce) - else - vim.schedule(function() - self:update_for_buffer(self.get_current_buffer(), should_debounce) - end) - end + if buffer then + self:update_for_buffer(buffer, should_debounce) + else + vim.schedule(function() + self:update_for_buffer(self.get_current_buffer(), should_debounce) + end) + end end) -------------------------------------------------- @@ -999,190 +1000,190 @@ end) -- Register some remote peer function Presence:register_peer(id, socket) - self.log:debug(string.format("Registering peer %s...", id)) + self.log:debug(string.format("Registering peer %s...", id)) - self.peers[id] = { - socket = socket, - workspace = nil, - } + self.peers[id] = { + socket = socket, + workspace = nil, + } - self.log:info(string.format("Registered peer %s", id)) + self.log:info(string.format("Registered peer %s", id)) end -- Unregister some remote peer function Presence:unregister_peer(id, peer) - self.log:debug(string.format("Unregistering peer %s... %s", id, vim.inspect(peer))) + self.log:debug(string.format("Unregistering peer %s... %s", id, vim.inspect(peer))) - -- Remove workspace if no other peers share the same workspace - -- Initialize to remove if the workspace differs from the local workspace, check peers below - local should_remove_workspace = peer.workspace ~= self.workspace + -- Remove workspace if no other peers share the same workspace + -- Initialize to remove if the workspace differs from the local workspace, check peers below + local should_remove_workspace = peer.workspace ~= self.workspace - local peers = {} - for peer_id, peer_data in pairs(self.peers) do - -- Omit peer from peers list - if peer_id ~= id then - peers[peer_id] = peer_data + local peers = {} + for peer_id, peer_data in pairs(self.peers) do + -- Omit peer from peers list + if peer_id ~= id then + peers[peer_id] = peer_data - -- Should not remove workspace if another peer shares the workspace - if should_remove_workspace and peer.workspace == peer_data.workspace then - should_remove_workspace = false - end - end - end + -- Should not remove workspace if another peer shares the workspace + if should_remove_workspace and peer.workspace == peer_data.workspace then + should_remove_workspace = false + end + end + end - self.peers = peers + self.peers = peers - -- Update workspaces if necessary - local workspaces = {} - if should_remove_workspace then - self.log:debug(string.format("Should remove workspace %s", peer.workspace)) - for workspace, data in pairs(self.workspaces) do - if workspace ~= peer.workspace then - workspaces[workspace] = data - end - end + -- Update workspaces if necessary + local workspaces = {} + if should_remove_workspace then + self.log:debug(string.format("Should remove workspace %s", peer.workspace)) + for workspace, data in pairs(self.workspaces) do + if workspace ~= peer.workspace then + workspaces[workspace] = data + end + end - self.workspaces = workspaces - end + self.workspaces = workspaces + end - self.log:info(string.format("Unregistered peer %s", id)) + self.log:info(string.format("Unregistered peer %s", id)) end -- Unregister some remote peer and set activity function Presence:unregister_peer_and_set_activity(id, peer) - self:unregister_peer(id, peer) - self:update() + self:unregister_peer(id, peer) + self:update() end -- Register a remote peer and sync its data function Presence:register_and_sync_peer(id, socket) - self:register_peer(id, socket) + self:register_peer(id, socket) - self.log:debug("Syncing data with newly registered peer...") + self.log:debug("Syncing data with newly registered peer...") - -- Initialize the remote peer's list including self - local peers = { - [self.id] = { - socket = self.socket, - workspace = self.workspace, - }, - } - for peer_id, peer in pairs(self.peers) do - if peer_id ~= id then - peers[peer_id] = peer - end - end + -- Initialize the remote peer's list including self + local peers = { + [self.id] = { + socket = self.socket, + workspace = self.workspace, + }, + } + for peer_id, peer in pairs(self.peers) do + if peer_id ~= id then + peers[peer_id] = peer + end + end - self:call_remote_method( - socket, - "sync_self", - { { - last_activity = self.last_activity, - peers = peers, - workspaces = self.workspaces, - } } - ) + self:call_remote_method( + socket, + "sync_self", + { { + last_activity = self.last_activity, + peers = peers, + workspaces = self.workspaces, + } } + ) end -- Register self to any remote Neovim instances -- Simply emits to all nvim sockets as we have not yet been synced with peer list function Presence:register_self() - self:get_nvim_socket_paths(function(sockets) - if #sockets == 0 then - self.log:debug("No other remote nvim instances") - return - end + self:get_nvim_socket_paths(function(sockets) + if #sockets == 0 then + self.log:debug("No other remote nvim instances") + return + end - self.log:debug(string.format("Registering as a new peer to %d instance(s)...", #sockets)) + self.log:debug(string.format("Registering as a new peer to %d instance(s)...", #sockets)) - -- Register and sync state with one of the sockets - self:call_remote_method(sockets[1], "register_and_sync_peer", { self.id, self.socket }) + -- Register and sync state with one of the sockets + self:call_remote_method(sockets[1], "register_and_sync_peer", { self.id, self.socket }) - if #sockets == 1 then - return - end + if #sockets == 1 then + return + end - for i = 2, #sockets do - self:call_remote_method(sockets[i], "register_peer", { self.id, self.socket }) - end - end) + for i = 2, #sockets do + self:call_remote_method(sockets[i], "register_peer", { self.id, self.socket }) + end + end) end -- Unregister self to all peers function Presence:unregister_self() - local self_as_peer = { - socket = self.socket, - workspace = self.workspace, - } + local self_as_peer = { + socket = self.socket, + workspace = self.workspace, + } - local i = 1 - for id, peer in pairs(self.peers) do - if self.options.auto_update and i == 1 then - self.log:debug(string.format("Unregistering self and setting activity for peer %s...", id)) - self:call_remote_method(peer.socket, "unregister_peer_and_set_activity", { self.id, self_as_peer }) - else - self.log:debug(string.format("Unregistering self to peer %s...", id)) - self:call_remote_method(peer.socket, "unregister_peer", { self.id, self_as_peer }) - end - i = i + 1 - end + local i = 1 + for id, peer in pairs(self.peers) do + if self.options.auto_update and i == 1 then + self.log:debug(string.format("Unregistering self and setting activity for peer %s...", id)) + self:call_remote_method(peer.socket, "unregister_peer_and_set_activity", { self.id, self_as_peer }) + else + self.log:debug(string.format("Unregistering self to peer %s...", id)) + self:call_remote_method(peer.socket, "unregister_peer", { self.id, self_as_peer }) + end + i = i + 1 + end end -- Sync self with data from a remote peer function Presence:sync_self(data) - self.log:debug(string.format("Syncing data from remote peer...", vim.inspect(data))) + self.log:debug(string.format("Syncing data from remote peer...", vim.inspect(data))) - for key, value in pairs(data) do - self[key] = value - end + for key, value in pairs(data) do + self[key] = value + end - self.log:info("Synced runtime data from remote peer") + self.log:info("Synced runtime data from remote peer") end -- Sync activity set by self to all peers function Presence:sync_self_activity() - local self_as_peer = { - socket = self.socket, - workspace = self.workspace, - } + local self_as_peer = { + socket = self.socket, + workspace = self.workspace, + } - for id, peer in pairs(self.peers) do - self.log:debug(string.format("Syncing activity to peer %s...", id)) + for id, peer in pairs(self.peers) do + self.log:debug(string.format("Syncing activity to peer %s...", id)) - local peers = { [self.id] = self_as_peer } - for peer_id, peer_data in pairs(self.peers) do - if peer_id ~= id then - peers[peer_id] = { - socket = peer_data.socket, - workspace = peer_data.workspace, - } - end - end + local peers = { [self.id] = self_as_peer } + for peer_id, peer_data in pairs(self.peers) do + if peer_id ~= id then + peers[peer_id] = { + socket = peer_data.socket, + workspace = peer_data.workspace, + } + end + end - self:call_remote_method( - peer.socket, - "sync_peer_activity", - { { - last_activity = self.last_activity, - peers = peers, - workspaces = self.workspaces, - } } - ) - end + self:call_remote_method( + peer.socket, + "sync_peer_activity", + { { + last_activity = self.last_activity, + peers = peers, + workspaces = self.workspaces, + } } + ) + end end -- Sync activity set by peer function Presence:sync_peer_activity(data) - self.log:debug(string.format("Syncing peer activity %s...", vim.inspect(data))) - self:cancel() - self:sync_self(data) + self.log:debug(string.format("Syncing peer activity %s...", vim.inspect(data))) + self:cancel() + self:sync_self(data) end function Presence:stop() - self.log:debug("Disconnecting from Discord...") - self.discord:disconnect(function() - self.log:info("Disconnected from Discord") - end) + self.log:debug("Disconnecting from Discord...") + self.discord:disconnect(function() + self.log:info("Disconnected from Discord") + end) end -------------------------------------------------- @@ -1191,98 +1192,98 @@ end -- FocusGained events force-update the presence for the current buffer unless it's a quickfix window function Presence:handle_focus_gained() - self.log:debug("Handling FocusGained event...") + self.log:debug("Handling FocusGained event...") - -- Skip a potentially extraneous update call on initial startup if tmux is being used - -- (See https://github.com/neovim/neovim/issues/14572) - if next(self.last_activity) == nil and os.getenv("TMUX") then - self.log:debug("Skipping presence update for FocusGained event triggered by tmux...") - return - end + -- Skip a potentially extraneous update call on initial startup if tmux is being used + -- (See https://github.com/neovim/neovim/issues/14572) + if next(self.last_activity) == nil and os.getenv("TMUX") then + self.log:debug("Skipping presence update for FocusGained event triggered by tmux...") + return + end - if vim.bo.filetype == "qf" then - self.log:debug("Skipping presence update for quickfix window...") - return - end + if vim.bo.filetype == "qf" then + self.log:debug("Skipping presence update for quickfix window...") + return + end - self:update() + self:update() end -- TextChanged events debounce current buffer presence updates function Presence:handle_text_changed() - self.log:debug("Handling TextChanged event...") - self:update(nil, true) + self.log:debug("Handling TextChanged event...") + self:update(nil, true) end -- VimLeavePre events unregister the leaving instance to all peers and sets activity for the first peer function Presence:handle_vim_leave_pre() - self.log:debug("Handling VimLeavePre event...") - self:unregister_self() - self:cancel() + 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 function Presence:handle_win_enter() - self.log:debug("Handling WinEnter event...") + self.log:debug("Handling WinEnter event...") - vim.schedule(function() - if vim.bo.filetype == "qf" then - self.log:debug("Skipping presence update for quickfix window...") - return - end + vim.schedule(function() + if vim.bo.filetype == "qf" then + self.log:debug("Skipping presence update for quickfix window...") + return + end - self:update() - end) + self:update() + end) end -- WinLeave events cancel the current buffer presence function Presence:handle_win_leave() - self.log:debug("Handling WinLeave event...") + self.log:debug("Handling WinLeave event...") - local current_window = vim.api.nvim_get_current_win() + local current_window = vim.api.nvim_get_current_win() - vim.schedule(function() - -- Avoid canceling presence when switching to a quickfix window - if vim.bo.filetype == "qf" then - self.log:debug("Not canceling presence due to switching to quickfix window...") - return - end + vim.schedule(function() + -- Avoid canceling presence when switching to a quickfix window + if vim.bo.filetype == "qf" then + self.log:debug("Not canceling presence due to switching to quickfix window...") + return + end - -- Avoid canceling presence when switching between windows - if current_window ~= vim.api.nvim_get_current_win() then - self.log:debug("Not canceling presence due to switching to a window within the same instance...") - return - end + -- Avoid canceling presence when switching between windows + if current_window ~= vim.api.nvim_get_current_win() then + self.log:debug("Not canceling presence due to switching to a window within the same instance...") + return + end - self.log:debug("Canceling presence due to leaving window...") - self:cancel() - end) + self.log:debug("Canceling presence due to leaving window...") + self:cancel() + end) end -- BufEnter events force-update the presence for the current buffer unless it's a quickfix window function Presence:handle_buf_enter() - self.log:debug("Handling BufEnter event...") + self.log:debug("Handling BufEnter event...") - if vim.bo.filetype == "qf" then - self.log:debug("Skipping presence update for quickfix window...") - return - end + if vim.bo.filetype == "qf" then + self.log:debug("Skipping presence update for quickfix window...") + return + end - self:update() + self:update() end -- 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...") + self.log:debug("Handling BufAdd event...") - vim.schedule(function() - if vim.bo.filetype == "qf" then - self.log:debug("Skipping presence update for quickfix window...") - return - end + vim.schedule(function() + if vim.bo.filetype == "qf" then + self.log:debug("Skipping presence update for quickfix window...") + return + end - self:update() - end) + self:update() + end) end return Presence diff --git a/lua/presence/plugin_managers.lua b/lua/presence/plugin_managers.lua index 3789ba3..7b92a43 100644 --- a/lua/presence/plugin_managers.lua +++ b/lua/presence/plugin_managers.lua @@ -1,5 +1,5 @@ -- Different plugin manager names return { - ["packer"] = "packer", - ["vim-plug"] = "vim-plug", + ["packer"] = "packer", + ["vim-plug"] = "vim-plug", }