diff --git a/lua/preview/reload.lua b/lua/preview/reload.lua new file mode 100644 index 0000000..fd64309 --- /dev/null +++ b/lua/preview/reload.lua @@ -0,0 +1,108 @@ +local M = {} + +local PORT = 5554 +local server_handle = nil +local clients = {} + +local function make_script(port) + return '' +end + +function M.start(port) + port = port or PORT + if server_handle then + return + end + local server = vim.uv.new_tcp() + server:bind('127.0.0.1', port) + server:listen(128, function(err) + if err then + return + end + local client = vim.uv.new_tcp() + server:accept(client) + local buf = '' + client:read_start(function(read_err, data) + if read_err or not data then + if not client:is_closing() then + client:close() + end + return + end + buf = buf .. data + if buf:find('\r\n\r\n') or buf:find('\n\n') then + client:read_stop() + local first_line = buf:match('^([^\r\n]+)') + if first_line and first_line:find('/__live/events', 1, true) then + local response = 'HTTP/1.1 200 OK\r\n' + .. 'Content-Type: text/event-stream\r\n' + .. 'Cache-Control: no-cache\r\n' + .. 'Access-Control-Allow-Origin: *\r\n' + .. '\r\n' + client:write(response) + table.insert(clients, client) + else + client:close() + end + end + end) + end) + server_handle = server +end + +function M.stop() + for _, c in ipairs(clients) do + if not c:is_closing() then + c:close() + end + end + clients = {} + if server_handle then + server_handle:close() + server_handle = nil + end +end + +function M.broadcast() + local event = 'event: reload\ndata: {}\n\n' + local alive = {} + for _, c in ipairs(clients) do + if not c:is_closing() then + local ok = pcall(function() + c:write(event) + end) + if ok then + table.insert(alive, c) + end + end + end + clients = alive +end + +function M.inject(path, port) + port = port or PORT + local f = io.open(path, 'r') + if not f then + return + end + local content = f:read('*a') + f:close() + local script = make_script(port) + local new_content, n = content:gsub('', script .. '\n', 1) + if n == 0 then + new_content = content .. '\n' .. script + end + local fw = io.open(path, 'w') + if not fw then + return + end + fw:write(new_content) + fw:close() +end + +return M diff --git a/spec/reload_spec.lua b/spec/reload_spec.lua new file mode 100644 index 0000000..12b7aac --- /dev/null +++ b/spec/reload_spec.lua @@ -0,0 +1,58 @@ +describe('reload', function() + local reload + + before_each(function() + package.loaded['preview.reload'] = nil + reload = require('preview.reload') + end) + + after_each(function() + reload.stop() + end) + + describe('inject', function() + it('injects script before ', function() + local path = os.tmpname() + local f = io.open(path, 'w') + f:write('

hello

') + f:close() + + reload.inject(path) + + local fr = io.open(path, 'r') + local content = fr:read('*a') + fr:close() + os.remove(path) + + assert.is_truthy(content:find('EventSource', 1, true)) + local script_pos = content:find('EventSource', 1, true) + local body_pos = content:find('', 1, true) + assert.is_truthy(body_pos) + assert.is_true(script_pos < body_pos) + end) + + it('appends script when no ', function() + local path = os.tmpname() + local f = io.open(path, 'w') + f:write('

hello

') + f:close() + + reload.inject(path) + + local fr = io.open(path, 'r') + local content = fr:read('*a') + fr:close() + os.remove(path) + + assert.is_truthy(content:find('EventSource', 1, true)) + end) + end) + + describe('broadcast', function() + it('does not error with no clients', function() + assert.has_no.errors(function() + reload.broadcast() + end) + end) + end) +end)