From 3b57f9457a65f49007d9ced3a655d83fff1c1a91 Mon Sep 17 00:00:00 2001 From: Folkert Kevelam Date: Mon, 18 Aug 2025 23:02:07 +0200 Subject: [PATCH] Initial commit --- lua/MarkdownPreviewer/app.lua | 88 ++++++++++++++ lua/MarkdownPreviewer/init.lua | 70 +++++++++++ lua/MarkdownPreviewer/msgpack.lua | 183 +++++++++++++++++++++++++++++ lua/MarkdownPreviewer/throttle.lua | 31 +++++ 4 files changed, 372 insertions(+) create mode 100644 lua/MarkdownPreviewer/app.lua create mode 100644 lua/MarkdownPreviewer/init.lua create mode 100644 lua/MarkdownPreviewer/msgpack.lua create mode 100644 lua/MarkdownPreviewer/throttle.lua diff --git a/lua/MarkdownPreviewer/app.lua b/lua/MarkdownPreviewer/app.lua new file mode 100644 index 0000000..1d2e6af --- /dev/null +++ b/lua/MarkdownPreviewer/app.lua @@ -0,0 +1,88 @@ +local msgpack = require"MarkdownPreviewer.msgpack" +chansend = vim.fn.chansend + +module = {} + +local function ToUint32(data) + local len = data + t = {} + for i=4,1,-1 do + t[i] = math.fmod(len, 256) + len = math.floor(len / 256) + end + return string.char(unpack(t)) +end + +local function Message(data) + local internal = msgpack.ToMsgPack(data) + return ToUint32(#internal) .. internal +end + +app = { + cmd = nil, + channel = nil +} + +function app:init(on_exit) + if self.channel then + return + end + + local cwd = debug.getinfo(1, 'S').source:sub(2):match('(.*[/\\])') + self.channel = vim.fn.jobstart(self.cmd, { + cwd = cwd, + stderr_buffered = true, + on_exit = function() + vim.fn.chanclose(self.channel) + self.channel = nil + on_exit() + end, + on_stderr = function(channel, data, name) + print(vim.inspect(data)) + end, + on_stdout = function(channel, data, name) + print(vim.inspect(data)) + end + }) +end + +function app:show(content) + if self.channel == nil then + return + end + + chansend(self.channel, Message({show = content})) +end + +function app:scroll(content) + if self.channel == nil then + return + end + + chansend(self.channel, Message({scroll = content})) +end + + +function app:stop() + if self.channel then + vim.fn.jobstop(self.channel) + end +end + +function module.setup() + o = {} + + setmetatable(o, app) + app.__index = app + + o.cmd = { + "../../Server/run.sh", + "../../Server/venv", + 'python3', + '../../Server/server.py' + } + + return o +end + +return module diff --git a/lua/MarkdownPreviewer/init.lua b/lua/MarkdownPreviewer/init.lua new file mode 100644 index 0000000..949f55c --- /dev/null +++ b/lua/MarkdownPreviewer/init.lua @@ -0,0 +1,70 @@ +local client = require"MarkdownPreviewer.app" +local throttle = require"MarkdownPreviewer.throttle" + +local nvim_buf_get_lines = vim.api.nvim_buf_get_lines +local nvim_create_augroup = vim.api.nvim_create_augroup +local nvim_create_autocmd = vim.api.nvim_create_autocmd +local nvim_del_augroup_by_id = vim.api.nvim_del_augroup_by_id +local concat = table.concat + +local function get_buf_content(bufnr) + local data = concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n'):gsub('%s*$', '') + return data +end + +module = {} + +local server_connection + +function module.setup() + server_connection = client.setup() +end + +function module.open() + augroup = nvim_create_augroup("MarkdownPreviewActiveAugroup", {clear = true}) + + server_connection:init(function() + augroup = nvim_del_augroup_by_id(augroup) + end) + + local bufnr = vim.api.nvim_get_current_buf() + server_connection:show(get_buf_content(bufnr)) + server_connection:scroll(vim.fn.line('.')) + + nvim_create_autocmd("BufWritePost", { + group = augroup, + buffer = bufnr, + callback = function() + server_connection:show(get_buf_content(bufnr)) + end, + }) + + nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, { + group = augroup, + buffer = bufnr, + callback = function() + server_connection:scroll(vim.fn.line('.')) + end, + }) + + local function show() + server_connection:show(get_buf_content(bufnr)) + end + + nvim_create_autocmd({"TextChanged", "TextChangedI", "TextChangedP" }, { + group = augroup, + buffer = bufnr, + callback = function() + throttle.run_fn(show) + end, + }) + +end + +function module.close() + if server_connection then + server_connection:stop() + end +end + +return module diff --git a/lua/MarkdownPreviewer/msgpack.lua b/lua/MarkdownPreviewer/msgpack.lua new file mode 100644 index 0000000..1637307 --- /dev/null +++ b/lua/MarkdownPreviewer/msgpack.lua @@ -0,0 +1,183 @@ +module = {} + +local bit8 = 256 +local bit16 = 65535 +local bit32 = 4294967295 +local bit64 = 18446744070000001024 + +local function ToUint16(length) + local len = length + t = {} + for i=2,1,-1 do + t[i] = math.fmod(len,256) + len = math.floor(len / 256) + end + return string.char(unpack(t)) +end + +local function ToUint32(length) + local len = length + t = {} + for i=4,1,-1 do + t[i] = math.fmod(len,256) + len = math.floor(len / 256) + end + return string.char(unpack(t)) +end + +local function ToUint64(length) + local len = length + t = {} + for i=8,1,-1 do + t[i] = math.fmod(len,256) + len = math.floor(len / 256) + end + return string.char(unpack(t)) +end + +local function is_array(t) + return type(t) == 'table' and (#t > 0 or next(t) == nil) +end + +local function NilToMsgPack() + return string.char(0xc0) +end + +local function StringToMsgPack(operand) + local str_len = #operand + + if str_len <= 31 then + local header = string.char(0xa0 + str_len) + return header .. operand + elseif str_len <= bit8 - 1 then + local header = string.char(0xd9) + return header .. string.char(str_len) .. operand + elseif str_len <= bit16 - 1 then + local header = string.char(0xda) + return header .. ToUint16(str_len) .. operand + elseif str_len <= bit32 - 1 then + local header = string.char(0xdb) + return header .. ToUint32(str_len) .. operand + else + print("String too large") + return nil + end +end + +local function NumberToMsgPack(operand) + if operand > 0 then + if operand <= 127 then + return string.char(operand) + elseif operand <= bit8 - 1 then + return string.char(0xcc) .. string.char(operand) + elseif operand <= bit16 - 1 then + return string.char(0xcd) .. ToUint16(operand) + elseif operand <= bit32 - 1 then + return string.char(0xce) .. ToUint32(operand) + elseif operand <= bit64 - 1 then + return string.char(0xcf) .. ToUint64(operand) + end + end + + return nil +end + +local function ArrayToMsgPack(operand) + local len = #operand + + local data = "" + + if len <= 15 then + data = data .. string.char(0x90 + len) + elseif len <= bit16 - 1 then + data = data .. string.char(0xdc) .. ToUint16(len) + elseif len <= bit32 - 1 then + data = data .. string.char(0xdd) .. ToUint32(len) + end + + for i=1,len do + local element = operand[i] + if type(element) == 'nil' then + data = data .. NilToMsgPack() + elseif type(element) == 'number' then + data = data .. NumberToMsgPack(element) + elseif type(element) == 'string' then + data = data .. StringToMsgPack(element) + elseif type(element) == 'table' then + if is_array(element) then + data = data .. ArrayToMsgPack(element) + else + data = data .. TableToMsgPack(element) + end + end + end + + return data +end + +local function TableToMsgPack(operand) + local len = 0 + for _,v in pairs(operand) do + len = len + 1 + end + + local data = "" + + if len <= 15 then + data = data .. string.char(0x80 + len) + elseif len <= bit16 - 1 then + data = data .. string.char(0xde) .. ToUint16(len) + elseif len <= bit32 - 1 then + data = data .. string.char(0xdf) .. ToUint32(len) + end + + for k,v in pairs(operand) do + if type(k) == 'nil' then + data = data .. NilToMsgPack() + elseif type(k) == 'number' then + data = data .. NumberToMsgPack(k) + elseif type(k) == 'string' then + data = data .. StringToMsgPack(k) + elseif type(k) == 'table' then + if is_array(k) then + data = data .. ArrayToMsgPack(k) + else + data = data .. TableToMsgPack(k) + end + end + + if type(v) == 'nil' then + data = data .. NilToMsgPack() + elseif type(v) == 'number' then + data = data .. NumberToMsgPack(v) + elseif type(v) == 'string' then + data = data .. StringToMsgPack(v) + elseif type(v) == 'table' then + if is_array(v) then + data = data .. ArrayToMsgPack(v) + else + data = data .. TableToMsgPack(v) + end + end + end + + return data +end + +function module.ToMsgPack(operand) + if type(operand) == 'nil' then + return NilToMsgPack() + elseif type(operand) == 'number' then + return NumberToMsgPack() + elseif type(operand) == 'string' then + return StringToMsgPack() + elseif type(operand) == 'table' then + if is_array(operand) then + return ArrayToMsgPack(operand) + else + return TableToMsgPack(operand) + end + end +end + +return module diff --git a/lua/MarkdownPreviewer/throttle.lua b/lua/MarkdownPreviewer/throttle.lua new file mode 100644 index 0000000..536f4bb --- /dev/null +++ b/lua/MarkdownPreviewer/throttle.lua @@ -0,0 +1,31 @@ +local module = {} + +local defer = 1000 +local throttled = false +local fn_to_run = nil + +local function throttle() + if fn_to_run then + fn_to_run() + fn_to_run = nil + vim.defer_fn(throttle, defer) + else + throttled = false + end +end + +function module.run_fn(fn, args) + if throttled == true then + local function func() + fn(args) + end + + fn_to_run = func + else + fn(args) + throttled = true + vim.defer_fn(throttle, defer) + end +end + +return module