diff --git a/lua/easytables/export.lua b/lua/easytables/export.lua index ae14945..c51fe68 100644 --- a/lua/easytables/export.lua +++ b/lua/easytables/export.lua @@ -1,11 +1,20 @@ +local o = require("easytables.options") + local M = {} ---comment ---@param content string ---@param width number ----@return table +---@return string function M:export_cell(content, width) - return "| " .. content .. string.rep(" ", width - #content) .. " " + local padding = string.rep(" ", o.options.export.markdown.padding) + + return + o.options.export.markdown.characters.vertical + .. padding + .. content + .. string.rep(" ", width - #content) + .. padding end ---Exports a line to a string @@ -21,7 +30,7 @@ function M:export_line(line, widths) str = str .. self:export_cell(cell, width) end - return str .. "|" + return str .. o.options.export.markdown.characters.vertical end ---comment @@ -32,10 +41,13 @@ function M:create_header_line(widths) -- No idea why, but "ipairs" is required otherwise lua complains for _, width in ipairs(widths) do - str = str .. "|" .. string.rep("-", width + 2) + str = + str + .. o.options.export.markdown.characters.vertical + .. string.rep(o.options.export.markdown.characters.horizontal, width) end - return str .. "|" + return str .. o.options.export.markdown.characters.vertical end ---comment diff --git a/lua/easytables/init.lua b/lua/easytables/init.lua index 9a4d214..1e2e77d 100644 --- a/lua/easytables/init.lua +++ b/lua/easytables/init.lua @@ -1,7 +1,7 @@ local table = require("easytables.table") local window = require("easytables.window") local inputHelper = require("easytables.input") -local optionsHelper = require("easytables.options") +local o = require("easytables.options") local function create_new_table(cols, rows) local markdown_table = table:create(cols, rows) @@ -16,7 +16,8 @@ end ---Initialize `easytables` with the given options. This function **must** be called. ---@param options table See options.lua for available options local function setup(options) - optionsHelper.merge_options(options) + options = options or {} + o.merge_options(options) vim.api.nvim_create_user_command( "EasyTablesCreateNew", diff --git a/lua/easytables/input.lua b/lua/easytables/input.lua index 6cda316..f1547a2 100644 --- a/lua/easytables/input.lua +++ b/lua/easytables/input.lua @@ -2,7 +2,7 @@ local string = require("string") ---Extracts the column info from the input ---@param raw_input string ----@return number, number +---@return table local function extract_column_info(raw_input) local _, _, cols, create_singular, rows = string.find(raw_input, "(%d+)(x?)(%d*)") diff --git a/lua/easytables/options.lua b/lua/easytables/options.lua index c19d072..3981d44 100644 --- a/lua/easytables/options.lua +++ b/lua/easytables/options.lua @@ -1,6 +1,8 @@ -- All available options are listed below. The default values are shown. -local DEFAULT = { +local options = { table = { + -- Whether to enable the header by default + header_enabled_by_default = true, window = { preview_title = "Table Preview", prompt_title = "Cell content", @@ -14,9 +16,6 @@ local DEFAULT = { -- Filler character for empty cells filler = " ", align = "left", - -- Padding around the cell content, applied BOTH left AND right - -- E.g: padding = 1, content = "foo" -> " foo " - padding = 1, }, -- Characters used to draw the table -- Do not worry about multibyte characters, they are handled correctly @@ -39,17 +38,28 @@ local DEFAULT = { header_horizontal = "═", } }, + export = { + markdown = { + -- Padding around the cell content, applied BOTH left AND right + -- E.g: padding = 1, content = "foo" -> " foo " + padding = 1, + -- What markdown characters are used for the export, you probably + -- don't want to change these + characters = { + horizontal = "-", + vertical = "|", + } + } + } } -- You can ignore everything below this line -local options = {} - -local function tableMerge(t1, t2) +local function merge_tables(t1, t2) for k, v in pairs(t2) do if type(v) == "table" then if type(t1[k] or false) == "table" then - tableMerge(t1[k] or {}, t2[k] or {}) + merge_tables(t1[k] or {}, t2[k] or {}) else t1[k] = v end @@ -57,11 +67,10 @@ local function tableMerge(t1, t2) t1[k] = v end end - return t1 end local function merge_options(user_options) - options = tableMerge(DEFAULT, user_options) + merge_tables(options, user_options) end return { diff --git a/lua/easytables/table.lua b/lua/easytables/table.lua index 637ffba..581569e 100644 --- a/lua/easytables/table.lua +++ b/lua/easytables/table.lua @@ -1,3 +1,5 @@ +local o = require("easytables.options") + local M = {}; function M:create(cols, rows) @@ -14,7 +16,12 @@ function M:create(cols, rows) col = 1, row = 1, } - self.header_enabled = true + + if rows > 1 then + self.header_enabled = o.options.table.header_enabled_by_default + else + self.header_enabled = false + end return self end @@ -28,6 +35,11 @@ function M:value_at(row, col) end function M:toggle_header() + if #self.table == 1 then + error("Cannot toggle header if table has only one row") + return + end + self.header_enabled = not self.header_enabled end @@ -60,14 +72,17 @@ function M:get_largest_length() return largest end -function M:get_widths_for_columns( - min_width --[[ int ]], - should_use_strwidth --[[ bool ]] -) -- table +--- +---@param should_use_strwidth boolean +---@return table +function M:get_widths_for_columns(should_use_strwidth) local widths = {} for i = 1, #self.table[1] do - widths[i] = math.max(min_width, self:get_largest_length_for_column(i, should_use_strwidth)) + widths[i] = math.max( + o.options.table.cell.min_width, + self:get_largest_length_for_column(i, should_use_strwidth) + ) end return widths @@ -172,7 +187,7 @@ function M:move_highlight_down() end function M:get_cell_positions(col, row, widths) - local length = #"│" + local length = #o.options.table.border.vertical local start_position = 0 for i, _ in ipairs(self.table[row]) do @@ -193,50 +208,55 @@ function M:get_cell_positions(col, row, widths) return start_position, end_position end -function M:get_horizontal_border_width( - col, -- [[ int ]] - row, -- [[ int ]] - min_value_width -- [[ int ]] -) - local length = #"─" +--- +---@param col boolean +---@param row boolean +---@return number, number +function M:get_horizontal_border_width(col, row) + local length = #o.options.table.border.horizontal local start_position = 0 - local widths = self:get_widths_for_columns(min_value_width, true) + local widths = self:get_widths_for_columns(true) for i, _ in ipairs(self.table[1]) do if i == col then break end - start_position = start_position + math.max(min_value_width, widths[i]) * length + start_position = + start_position + + math.max(o.options.table.cell.min_width, widths[i]) * length if row == 1 then - start_position = start_position + #"┬" + start_position = start_position + #o.options.table.border.top_t else - start_position = start_position + #"┼" + start_position = start_position + #o.options.table.border.cross end end local end_position = 0 if col == 1 then - end_position = #"┬" + end_position = #o.options.table.border.top_t else - end_position = #"┤" + end_position = #o.options.table.border.right_t end - end_position = end_position + start_position + math.max(min_value_width, widths[col]) * length + end_position = + end_position + + start_position + + math.max(o.options.table.cell.min_width, widths[col]) * length if row == 1 then if col == 1 then - end_position = end_position + #"┬" + end_position = end_position + #o.options.table.border.top_t else - end_position = end_position + #"┐" + end_position = end_position + #o.options.table.border.top_right end else if col == 1 then - end_position = end_position + #"├" + end_position = end_position + #o.options.table.border.left_t else - end_position = end_position + #"┤" + end_position = end_position + #o.options.table.border.right_t end end diff --git a/lua/easytables/tablebuilder.lua b/lua/easytables/tablebuilder.lua index 76e769d..b87b2e9 100644 --- a/lua/easytables/tablebuilder.lua +++ b/lua/easytables/tablebuilder.lua @@ -1,27 +1,6 @@ -local M = {}; +local o = require("easytables.options") -DEFAULT_DRAW_REPRESENTATION_OPTIONS = { - min_width = 3, - filler = " ", - top_left = "┌", - top_right = "┐", - bottom_left = "└", - bottom_right = "┘", - horizontal = "─", - vertical = "│", - left_t = "├", - right_t = "┤", - top_t = "┬", - bottom_t = "┴", - cross = "┼", - header_left_t = "╞", - header_right_t = "╡", - header_bottom_t = "╧", - header_cross = "╪", - header_horizontal = "═", -} - -function create_horizontal_line(cell_widths, left, middle, right, middle_t) +local function create_horizontal_line(cell_widths, left, middle, right, middle_t) local string = "" string = string .. left @@ -39,81 +18,92 @@ function create_horizontal_line(cell_widths, left, middle, right, middle_t) return string end --- Creates a horizontal divider like this: --- `create_horizontal_divider(5, 5, {variant = "top"})`: --- ┌─────┬─────┬─────┬─────┬─────┐ --- `create_horizontal_divider(5, 5, {variant = "between"})`: --- ├─────┼─────┼─────┼─────┼─────┤ --- `create_horizontal_divider(5, 5, {variant = "bottom"})`: --- └─────┴─────┴─────┴─────┴─────┘ -function create_horizontal_divider( +---Creates a horizontal divider like this: +---`create_horizontal_divider(5, 5, {variant = "top"})`: +---┌─────┬─────┬─────┬─────┬─────┐ +---`create_horizontal_divider(5, 5, {variant = "between"})`: +---├─────┼─────┼─────┼─────┼─────┤ +---`create_horizontal_divider(5, 5, {variant = "bottom"})`: +---└─────┴─────┴─────┴─────┴─────┘ +---@param table table +---@param[opt="between"] variant string Either "top", "between" or "bottom" +---@return string +local function create_horizontal_divider( table, - options -- [[ table ]] -- optional + variant ) - local options = options or {} - local top_left = options.top_left or DEFAULT_DRAW_REPRESENTATION_OPTIONS.top_left - local top_right = options.top_right or DEFAULT_DRAW_REPRESENTATION_OPTIONS.top_right - local bottom_left = options.bottom_left or DEFAULT_DRAW_REPRESENTATION_OPTIONS.bottom_left - local bottom_right = options.bottom_right or DEFAULT_DRAW_REPRESENTATION_OPTIONS.bottom_right - local horizontal = options.horizontal or DEFAULT_DRAW_REPRESENTATION_OPTIONS.horizontal - local left_t = options.left_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.left_t - local right_t = options.right_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.right_t - local top_t = options.top_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.top_t - local bottom_t = options.bottom_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.bottom_t - local cross = options.cross or DEFAULT_DRAW_REPRESENTATION_OPTIONS.cross - local header_left_t = options.header_left_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.header_left_t - local header_right_t = options.header_right_t or DEFAULT_DRAW_REPRESENTATION_OPTIONS.header_right_t - local header_cross = options.header_cross or DEFAULT_DRAW_REPRESENTATION_OPTIONS.header_cross - local header_horizontal = options.header_horizontal or DEFAULT_DRAW_REPRESENTATION_OPTIONS.header_horizontal - local min_width = options.min_width or DEFAULT_DRAW_REPRESENTATION_OPTIONS.min_width - local variant = options.variant or "between" + variant = variant or "between" - local widths = table:get_widths_for_columns(min_width) + local widths = table:get_widths_for_columns() if variant == "top" then - return create_horizontal_line(widths, top_left, horizontal, top_right, top_t) + return create_horizontal_line( + widths, + o.options.table.border.top_left, + o.options.table.border.horizontal, + o.options.table.border.top_right, + o.options.table.border.top_t + ) elseif variant == "between" then - return create_horizontal_line(widths, left_t, horizontal, right_t, cross) + return create_horizontal_line( + widths, + o.options.table.border.left_t, + o.options.table.border.horizontal, + o.options.table.border.right_t, + o.options.table.border.cross + ) elseif variant == "bottom" then - return create_horizontal_line(widths, bottom_left, horizontal, bottom_right, bottom_t) + return create_horizontal_line( + widths, + o.options.table.border.bottom_left, + o.options.table.border.horizontal, + o.options.table.border.bottom_right, + o.options.table.border.bottom_t + ) elseif variant == "header" then - return create_horizontal_line(widths, header_left_t, header_horizontal, header_right_t, header_cross) + return create_horizontal_line( + widths, + o.options.table.border.header_left_t, + o.options.table.border.header_horizontal, + o.options.table.border.header_right_t, + o.options.table.border.header_cross + ) end + + return "" end -function table.draw_representation( - table, -- [[ table ]] - options -- [[ table ]] -- optional -) - local options = options or {} - local min_width = options.min_width or DEFAULT_DRAW_REPRESENTATION_OPTIONS.min_width - local filler = options.filler or DEFAULT_DRAW_REPRESENTATION_OPTIONS.filler - local vertical = options.vertical or DEFAULT_DRAW_REPRESENTATION_OPTIONS.vertical - +---Draws a table representation for the preview +---@param table table +---@return table +local function draw_representation(table) local representation = {} - local horizontal_divider = create_horizontal_divider(table, options) + local horizontal_divider = create_horizontal_divider(table, "between") - representation[#representation + 1] = create_horizontal_divider(table, { variant = "top" }) + representation[#representation + 1] = create_horizontal_divider(table, "top") - local column_widths = table:get_widths_for_columns(min_width) + local column_widths = table:get_widths_for_columns() for i = 1, table:rows_amount() do local line = "" + for j = 1, table:cols_amount() do local length = column_widths[j] local cell = table:value_at(i, j) local cell_width = vim.api.nvim_strwidth(cell) if cell_width < length then - cell = cell .. string.rep(filler, length - cell_width) + cell = cell + .. string.rep(o.options.table.cell.filler, length - cell_width) end - cell = vertical .. cell + -- Add left vertical divider + cell = o.options.table.border.vertical .. cell -- Add most right vertical divider if j == table:cols_amount() then - cell = cell .. vertical + cell = cell .. o.options.table.border.vertical end line = line .. cell @@ -122,25 +112,17 @@ function table.draw_representation( representation[#representation + 1] = line if i == 1 and table.header_enabled then - representation[#representation + 1] = create_horizontal_divider(table, { variant = "header" }) + representation[#representation + 1] = create_horizontal_divider(table, "header") elseif i ~= table:rows_amount() then representation[#representation + 1] = horizontal_divider end end - representation[#representation + 1] = create_horizontal_divider(table, { variant = "bottom" }) + representation[#representation + 1] = create_horizontal_divider(table, "bottom") return representation end -function table.from_representation(representation, options) - local opts = options or {} - - local table = {} - - for i = 1, #representation do - local character = representation[i] - end -end - -return table +return { + draw_representation = draw_representation +} diff --git a/lua/easytables/window.lua b/lua/easytables/window.lua index 0a9f2f1..8781d46 100644 --- a/lua/easytables/window.lua +++ b/lua/easytables/window.lua @@ -1,37 +1,33 @@ local table_builder = require("easytables.tablebuilder") local export = require("easytables.export") +local o = require("easytables.options") local math = require("math") local M = {} -DEFAULT_OPTIONS = { - title = "Table", - prompt_title = "Content", - width = 60, - height = 30, - min_value_width = 3, -} - function M:create(table, options) options = options or {} - self.title = options.title or DEFAULT_OPTIONS.title - self.prompt_title = options.prompt_title or DEFAULT_OPTIONS.prompt_title - self.width = options.width or DEFAULT_OPTIONS.width - self.height = options.height or DEFAULT_OPTIONS.height - self.min_value_width = options.min_value_width or DEFAULT_OPTIONS.min_value_width self.table = table self.previous_window = vim.api.nvim_get_current_win() return self end +function M:_get_width() + return 60 +end + +function M:_get_preview_height() + return 20 +end + function M:get_x() - return math.floor((vim.o.columns - self.width) / 2) + return math.floor((vim.o.columns - self:_get_width()) / 2) end function M:get_y() - return math.floor(((vim.o.lines - self.height) / 2) - 1) + return math.floor(((vim.o.lines - self:_get_preview_height()) / 2) - 1) end function M:_open_preview_window() @@ -40,30 +36,34 @@ function M:_open_preview_window() relative = "win", col = self:get_x(), row = self:get_y(), - width = self.width, - height = self.height, + width = self:_get_width(), + height = self:_get_preview_height(), style = "minimal", border = "rounded", - title = self.title, + title = o.options.table.window.preview_title, title_pos = "center", }) -- Disable default highlight - vim.api.nvim_set_option_value("winhighlight", "Normal:Normal", - { win = self.preview_window }) + vim.api.nvim_set_option_value( + "winhighlight", + "Normal:Normal", + { win = self.preview_window } + ) end function M:_open_prompt_window() self.prompt_buffer = vim.api.nvim_create_buf(false, false) self.prompt_window = vim.api.nvim_open_win(self.prompt_buffer, true, { relative = "win", + -- No idea why, but the window is shifted one cell to the right by default col = self:get_x() - 1, - row = self:get_y() + self.height + 2, - width = self.width, + row = self:get_y() + self:_get_preview_height() + 2, + width = self:_get_width(), height = 2, style = "minimal", border = "rounded", - title = self.prompt_title, + title = o.options.table.window.prompt_title, title_pos = "center", }) @@ -147,7 +147,9 @@ function M:close() vim.api.nvim_win_close(self.preview_window, true) vim.api.nvim_win_close(self.prompt_window, true) - vim.api.nvim_set_current_win(self.previous_window) + pcall(function() + vim.api.nvim_set_current_win(self.previous_window) + end) self.preview_window = nil self.prompt_window = nil