commit afbc1a263b520ff59f7d58316bd15f2064e930c9 Author: Syed Daanish Date: Wed Jun 25 15:03:22 2025 +0300 Initial commit diff --git a/lua/sequin.lua b/lua/sequin.lua new file mode 100644 index 0000000..2f764bc --- /dev/null +++ b/lua/sequin.lua @@ -0,0 +1,840 @@ +local M = {} + +local function format_table(data, columns_inp, window_width) + local highlights = {} + local function truncate(str, max_len) + local max_screen_cutoff = math.floor(window_width * 0.75) + str = tostring(str or "") + if vim.fn.strdisplaywidth(str) > max_screen_cutoff then + return str:sub(1, 20) .. "..." + elseif vim.fn.strdisplaywidth(str) > max_len then + if max_len <= 2 then + return str:sub(1, max_len) + else + return str:sub(1, max_len - 2) .. ".." + end + end + return str + end + local columns = {} + for _, col in ipairs(columns_inp) do + table.insert(columns, col.name) + end + local all_rows = {} + table.insert(all_rows, columns) + if #data == 0 then + table.insert(all_rows, { "Empty table!" }) + end + local function extract_first_nonempty_line(str) + str = tostring(str or ""):gsub("\r", "") + for line in str:gmatch("[^\n]*") do + local trimmed = line:match("^%s*(.-)%s*$") + if trimmed ~= "" then + local suffix = #trimmed == #str and "" or "..." + return trimmed .. suffix + end + end + return "" + end + local function sanitize_blob(val) + val = tostring(val or "") + local output = {} + local non_utf8_count = 0 + if val == "" then + return vim.NIL + end + for i = 1, #val do + local ch = val:sub(i, i) + if vim.fn.strdisplaywidth(ch) == 1 then + table.insert(output, ch) + else + non_utf8_count = non_utf8_count + 1 + table.insert(output, "�") + end + end + if non_utf8_count >= 3 then + return "BLOB" + else + return table.concat(output) + end + end + for _, row in ipairs(data) do + local new_row = {} + for i, col in ipairs(columns) do + local val = row[col] + if columns_inp[i].type:upper():match("BOOL") then + val = (val == "1" or val == 1) and "TRUE" or "FALSE" + end + if columns_inp[i].type:upper():match("BLOB") then + val = sanitize_blob(val) + end + if type(val) == "string" then + val = val:gsub("\r", "") + val = extract_first_nonempty_line(val) + if vim.fn.strdisplaywidth(val) > math.floor(window_width * 0.5) then + val = val:sub(1, 20) .. "..." + end + end + if val == nil or val == "" or val == vim.NIL or val == "vim.NIL" then + val = "∅" + end + table.insert(new_row, tostring(val)) + end + table.insert(all_rows, new_row) + end + local num_columns = #columns + local col_widths = {} + for i = 1, num_columns do + col_widths[i] = 0 + end + for _, row in ipairs(all_rows) do + for i = 1, num_columns do + local cell = row[i] or "" + local width = vim.fn.strdisplaywidth(cell) + if width > col_widths[i] then + col_widths[i] = width + end + end + end + local total_content_width = 0 + for _, w in ipairs(col_widths) do + total_content_width = total_content_width + w + end + local total_padding = 3 * num_columns + 1 + local remaining_space = window_width - total_padding - total_content_width + while remaining_space > 0 do + for i = 1, num_columns do + col_widths[i] = col_widths[i] + 1 + remaining_space = remaining_space - 1 + if remaining_space <= 0 then + break + end + end + end + local function draw_line(left, mid, right, hor) + local parts = { left } + for i = 1, num_columns do + table.insert(parts, string.rep(hor, col_widths[i] + 2)) + if i < num_columns then + table.insert(parts, mid) + end + end + table.insert(parts, right) + return table.concat(parts) + end + local imp_highlights = {} + local top_border = draw_line("┌", "┬", "┐", "─") + local mid_separator = draw_line("├", "┼", "┤", "─") + local bottom_border = draw_line("└", "┴", "┘", "─") + local formatted_lines = { top_border } + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" }) + for idx, row in ipairs(all_rows) do + local row_parts = { "│" } + table.insert(imp_highlights, { + #formatted_lines, + { 0, 2 }, + "SequinBorder", + }) + for i = 1, num_columns do + local text = truncate(row[i] or "", col_widths[i]) + local function pad_display(str, width) + local pad = width - vim.fn.strdisplaywidth(str) + if pad > 0 then + return str .. string.rep(" ", pad) + else + return str + end + end + local padded = " " .. pad_display(text, col_widths[i]) .. " " + local tmp_rp = table.concat(row_parts) + if idx == 1 then + local type = columns_inp[i].type:upper() + local pk = columns_inp[i].pk == 1 + local hl_suffix + if pk then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + col_widths[i] + 1 }, + "SequinPk", + }) + else + if type:match("INT") then + hl_suffix = "Int" + elseif type:match("CHAR") or type:match("TEXT") or type:match("CLOB") then + hl_suffix = "String" + elseif type:match("REAL") or type:match("FLOA") or type:match("DOUB") then + hl_suffix = "Float" + elseif type:match("BLOB") then + hl_suffix = "Blob" + elseif type:match("BOOL") then + hl_suffix = "Bool" + elseif type:match("DATE") or type:match("TIME") then + hl_suffix = "Date" + else + hl_suffix = "" + end + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + #(truncate(row[i] or "", col_widths[i])) + 1 }, + "SequinTitles" .. hl_suffix, + }) + end + else + if row[i] == "∅" then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + 4 }, + "SequinNull", + }) + end + if columns_inp[i].type:upper():match("BOOL") then + if row[i] == "TRUE" then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + 5 }, + "SequinTrue", + }) + elseif row[i] == "FALSE" then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + 6 }, + "SequinFalse", + }) + end + end + end + table.insert(row_parts, padded) + table.insert(row_parts, "│") + tmp_rp = table.concat(row_parts) + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp - 3, #tmp_rp - 1 }, + "SequinBorder", + }) + end + table.insert(formatted_lines, table.concat(row_parts)) + if idx == 1 then + table.insert(formatted_lines, mid_separator) + table.insert( + highlights, + { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" } + ) + else + local hg = idx % 2 == 0 and "SequinRow" or "SequinRowAlt" + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, hg }) + end + end + table.insert(formatted_lines, bottom_border) + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" }) + for _, v in ipairs(imp_highlights) do + table.insert(highlights, v) + end + return formatted_lines, highlights, col_widths +end + +local function format_table_columnless(data, window_width) + local highlights = {} + local function truncate(str, max_len) + local max_screen_cutoff = math.floor(window_width * 0.75) + str = tostring(str or "") + if vim.fn.strdisplaywidth(str) > max_screen_cutoff then + return str:sub(1, 20) .. "..." + elseif vim.fn.strdisplaywidth(str) > max_len then + if max_len <= 2 then + return str:sub(1, max_len) + else + return str:sub(1, max_len - 2) .. ".." + end + end + return str + end + if #data == 0 then + return { "Empty table!" }, {}, window_width + end + local columns = {} + for col, _ in pairs(data[1]) do + table.insert(columns, col) + end + table.sort(columns) + local all_rows = {} + table.insert(all_rows, columns) + if #data == 0 then + table.insert(all_rows, { "Empty table!" }) + end + local function extract_first_nonempty_line(str) + str = tostring(str or ""):gsub("\r", "") + for line in str:gmatch("[^\n]*") do + local trimmed = line:match("^%s*(.-)%s*$") + if trimmed ~= "" then + local suffix = #trimmed == #str and "" or "..." + return trimmed .. suffix + end + end + return "" + end + for _, row in ipairs(data) do + local new_row = {} + for _, col in ipairs(columns) do + local val = row[col] + if type(val) == "string" then + val = val:gsub("\r", "") + val = extract_first_nonempty_line(val) + if vim.fn.strdisplaywidth(val) > math.floor(window_width * 0.5) then + val = val:sub(1, 20) .. "..." + end + end + if val == nil or val == "" or val == vim.NIL or val == "vim.NIL" then + val = "∅" + end + table.insert(new_row, tostring(val)) + end + table.insert(all_rows, new_row) + end + local num_columns = #columns + local col_widths = {} + for i = 1, num_columns do + col_widths[i] = 0 + end + for _, row in ipairs(all_rows) do + for i = 1, num_columns do + local cell = row[i] or "" + local width = vim.fn.strdisplaywidth(cell) + if width > col_widths[i] then + col_widths[i] = width + end + end + end + local total_content_width = 0 + for _, w in ipairs(col_widths) do + total_content_width = total_content_width + w + end + local total_padding = 3 * num_columns + 1 + local remaining_space = window_width - total_padding - total_content_width + while remaining_space > 0 do + for i = 1, num_columns do + col_widths[i] = col_widths[i] + 1 + remaining_space = remaining_space - 1 + if remaining_space <= 0 then + break + end + end + end + local function draw_line(left, mid, right, hor) + local parts = { left } + for i = 1, num_columns do + table.insert(parts, string.rep(hor, col_widths[i] + 2)) + if i < num_columns then + table.insert(parts, mid) + end + end + table.insert(parts, right) + return table.concat(parts) + end + local imp_highlights = {} + local top_border = draw_line("┌", "┬", "┐", "─") + local mid_separator = draw_line("├", "┼", "┤", "─") + local bottom_border = draw_line("└", "┴", "┘", "─") + local formatted_lines = { top_border } + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" }) + for idx, row in ipairs(all_rows) do + local row_parts = { "│" } + table.insert(imp_highlights, { + #formatted_lines, + { 0, 2 }, + "SequinBorder", + }) + for i = 1, num_columns do + local text = truncate(row[i] or "", col_widths[i]) + local function pad_display(str, width) + local pad = width - vim.fn.strdisplaywidth(str) + if pad > 0 then + return str .. string.rep(" ", pad) + else + return str + end + end + local padded = " " .. pad_display(text, col_widths[i]) .. " " + local tmp_rp = table.concat(row_parts) + if idx == 1 then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + #(truncate(row[i] or "", col_widths[i])) + 1 }, + "SequinTitles", + }) + else + if row[i] == "∅" then + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp + 1, #tmp_rp + 4 }, + "SequinNull", + }) + end + end + table.insert(row_parts, padded) + table.insert(row_parts, "│") + tmp_rp = table.concat(row_parts) + table.insert(imp_highlights, { + #formatted_lines, + { #tmp_rp - 3, #tmp_rp - 1 }, + "SequinBorder", + }) + end + table.insert(formatted_lines, table.concat(row_parts)) + if idx == 1 then + table.insert(formatted_lines, mid_separator) + table.insert( + highlights, + { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" } + ) + else + local hg = idx % 2 == 0 and "SequinRow" or "SequinRowAlt" + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, hg }) + end + end + table.insert(formatted_lines, bottom_border) + table.insert(highlights, { #formatted_lines - 1, { 0, #formatted_lines[#formatted_lines] - 1 }, "SequinBorder" }) + for _, v in ipairs(imp_highlights) do + table.insert(highlights, v) + end + return formatted_lines, highlights, col_widths +end + +local function get_usable_win_width(win_id) + win_id = win_id or 0 + local total_width = vim.api.nvim_win_get_width(win_id) + if total_width == 0 then + return 0 + end + local wo = vim.wo[win_id] + local non_text_columns = 0 + if wo.number or wo.relativenumber then + non_text_columns = non_text_columns + wo.numberwidth + end + if wo.signcolumn == "yes" then + non_text_columns = non_text_columns + 2 + elseif wo.signcolumn == "auto" then + non_text_columns = non_text_columns + 1 + elseif wo.signcolumn:match("%d+") then + non_text_columns = non_text_columns + tonumber(wo.signcolumn) + end + non_text_columns = non_text_columns + wo.foldcolumn + return total_width - non_text_columns - 2 +end + +local function set_mark(buf, ns_id, highlight) + vim.api.nvim_buf_set_extmark( + buf, + ns_id, + highlight[1] + 3, + highlight[2][1], + { end_col = highlight[2][2], hl_group = highlight[3] } + ) +end + +local function main_menu(buf) + local tables = + vim.fn.system("sqlite3 " .. vim.b[buf].db .. " 'SELECT name FROM sqlite_master WHERE type = \\'table\\';'") + local tables_list = vim.split(tables, "\n", { trimempty = true }) + for i = 1, #tables_list do + tables_list[i] = " " .. tables_list[i] + end + table.insert(tables_list, 1, " sqlite_master") + table.insert(tables_list, 1, "") + table.insert(tables_list, 1, "Sequin table menu for database: " .. vim.b[buf].db .. "") + table.insert(tables_list, 1, "") + vim.cmd("setlocal modifiable") + vim.api.nvim_buf_set_lines(buf, 0, -1, false, tables_list) + set_mark( + buf, + vim.b[buf].ns_id, + { -2, { 0, #("Sequin table menu for database: " .. vim.b[buf].db .. "") }, "SequinTitles" } + ) + for i = 4, #tables_list do + set_mark(buf, vim.b[buf].ns_id, { i - 4, { 0, #tables_list[i] }, "SequinTitlesFloat" }) + end + vim.cmd("setlocal nomodifiable") +end + +local function define_color(name, color_fg, color_bg, bold, underline, italic) + if bold == nil then + bold = true + end + if underline == nil then + underline = false + end + if italic == nil then + italic = false + end + vim.api.nvim_set_hl(0, name, { fg = color_fg, bg = color_bg, bold = bold, underline = underline, italic = italic }) +end + +local function get_table_data(db, table_name, limit, p_no) + local query = string.format( + [[ + bash -c 'sqlite3 %s -json < pos then + return i + end + end + return -1 +end + +local function popup_data(buf, pos) + if pos[1] - 7 < 0 or pos[1] - 7 >= vim.b[buf].max - 20 * vim.b[buf].p_no or pos[1] - 7 >= 20 then + return + end + local data, cols, _ = get_table_data(vim.b[buf].db, vim.b[buf].table_name, 1, 20 * vim.b[buf].p_no + pos[1] - 7) + local col_widths = vim.b[buf].col_widths + local idx = find_column_at(pos[2], col_widths) + if idx == -1 then + return + end + local raw_value = data[1][cols[idx].name] + if raw_value == vim.NIL then + raw_value = "nil" + end + local str = raw_value and tostring(raw_value) or "nil" + str = str:gsub("\r", "") + local lines = vim.split(str, "\n", { plain = true }) + local width = 0 + for _, line in ipairs(lines) do + if #line > width then + width = #line + end + end + width = math.max(10, math.min(width + 4, math.ceil(vim.o.columns * 0.75))) + local height = math.max(10, math.min(#lines, math.ceil(vim.o.lines * 0.75))) + local row = math.floor((vim.o.lines - height) / 2 - 1) + local col = math.floor((vim.o.columns - width) / 2) + local popup_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(popup_buf, 0, -1, false, lines) + local popup_win = vim.api.nvim_open_win(popup_buf, true, { + relative = "editor", + row = row, + col = col, + width = width, + height = height, + style = "minimal", + border = "rounded", + }) + vim.api.nvim_set_option_value("filetype", "text", { buf = popup_buf }) + vim.api.nvim_set_option_value("wrap", true, { win = popup_win }) + vim.api.nvim_set_option_value("linebreak", true, { win = popup_win }) + vim.keymap.set("n", "", function() + if vim.api.nvim_win_is_valid(popup_win) then + vim.api.nvim_win_close(popup_win, true) + end + end, { buffer = popup_buf, nowait = true, noremap = true, silent = true }) +end + +local function run_exec(buf, query) + local raw_value = vim.fn.system(string.format( + [[ + bash -c 'sqlite3 %s <", function() + if vim.b[buf].state == "main" then + local table_name = string.sub(vim.fn.getline("."), 4) + vim.b[buf].table_name = table_name + vim.b[buf].max, vim.b[buf].col_widths = table_data(buf, 0) + vim.b[buf].state = "table" + vim.b[buf].p_no = 0 + elseif vim.b[buf].state == "table" then + local pos = vim.api.nvim_win_get_cursor(0) + popup_data(buf, pos) + end + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "n", function() + if vim.b[buf].state == "table" then + local p_no = vim.b[buf].p_no + 1 + local max = vim.b[buf].max + if p_no > (max / 20) then + p_no = math.floor(max / 20) + end + vim.b[buf].max, vim.b[buf].col_widths = table_data(buf, p_no) + vim.b[buf].p_no = p_no + elseif vim.b[buf].state == "select-table" then + local p_no = vim.b[buf].p_no + 1 + local max = vim.b[buf].max + if p_no > (max / 20) then + p_no = math.floor(max / 20) + end + vim.b[buf].p_no = p_no + vim.b[buf].max, vim.b[buf].col_widths = run_select(buf, vim.b[buf].ns_id, vim.b[buf].query) + end + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "N", function() + if vim.b[buf].state == "table" then + local p_no = vim.b[buf].p_no - 1 + if p_no < 0 then + p_no = 0 + end + vim.b[buf].max, vim.b[buf].col_widths = table_data(buf, p_no) + vim.b[buf].p_no = p_no + elseif vim.b[buf].state == "select-table" then + local p_no = vim.b[buf].p_no - 1 + if p_no < 0 then + p_no = 0 + end + vim.b[buf].p_no = p_no + vim.b[buf].max, vim.b[buf].col_widths = run_select(buf, vim.b[buf].ns_id, vim.b[buf].query) + end + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "", function() + main_menu(buf) + vim.b[buf].state = "main" + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "", function() + main_menu(buf) + vim.b[buf].state = "main" + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "r", function() + refresh(buf) + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "x", function() + vim.b[buf].state = "rand-query" + local query = vim.fn.input("Query: ") + if query == "" then + return + end + run_exec(buf, query) + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "g", function() + vim.b[buf].state = "select-table" + local query = vim.fn.input("Query: select ") + if query == "" then + return + end + vim.b[buf].p_no = 0 + vim.b[buf].query = query + vim.b[buf].max, vim.b[buf].col_widths = run_select(buf, vim.b[buf].ns_id, query) + end, { buffer = buf, noremap = true, silent = true }) + vim.b.no_git_diff = true + end, + }) + vim.api.nvim_create_autocmd("VimResized", { + pattern = "*.db,*.sqlite,*.sqlite3", + callback = function(args) + local buf = args.buf + refresh(buf) + end, + }) +end + +return M diff --git a/p.vim b/p.vim new file mode 100644 index 0000000..e978e5c --- /dev/null +++ b/p.vim @@ -0,0 +1,346 @@ +let SessionLoad = 1 +let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1 +let v:this_session=expand(":p") +silent only +silent tabonly +cd ~/main/sequin +if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == '' + let s:wipebuf = bufnr('%') +endif +let s:shortmess_save = &shortmess +if &shortmess =~ 'A' + set shortmess=aoOA +else + set shortmess=aoO +endif +badd +821 lua/sequin.lua +argglobal +%argdel +edit lua/sequin.lua +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +argglobal +let s:cpo_save=&cpo +set cpo&vim +inoremap llua require('nvim-autopairs.fastwrap').show() +nnoremap K lua vim.lsp.buf.hover() +nnoremap [d lua vim.diagnostic.goto_prev() +nnoremap ]d lua vim.diagnostic.goto_next() +nnoremap gl lua vim.diagnostic.open_float() +nnoremap gs lua vim.lsp.buf.signature_help() +nnoremap go lua vim.lsp.buf.type_definition() +nnoremap lua vim.lsp.buf.code_action() +nnoremap lua vim.lsp.buf.rename() +let &cpo=s:cpo_save +unlet s:cpo_save +setlocal keymap= +setlocal noarabic +setlocal autoindent +setlocal nobinary +setlocal nobreakindent +setlocal breakindentopt= +setlocal bufhidden= +setlocal buflisted +setlocal buftype= +setlocal nocindent +setlocal cinkeys=0{,0},0),0],:,0#,!^F,o,O,e +setlocal cinoptions= +setlocal cinscopedecls=public,protected,private +setlocal cinwords=if,else,while,do,for,switch +setlocal colorcolumn= +setlocal comments=:---,:-- +setlocal commentstring=--\ %s +setlocal complete=.,w,b,u,t +setlocal completefunc= +setlocal completeslash= +setlocal concealcursor= +setlocal conceallevel=0 +setlocal nocopyindent +setlocal nocursorbind +setlocal nocursorcolumn +setlocal cursorline +setlocal cursorlineopt=both +setlocal define=\\:p:r")."x.vim" +if filereadable(s:sx) + exe "source " . fnameescape(s:sx) +endif +let &g:so = s:so_save | let &g:siso = s:siso_save +set hlsearch +nohlsearch +doautoall SessionLoadPost +unlet SessionLoad +" vim: set ft=vim :