diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..1d04c6d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: bentribyte +buy_me_a_coffee: bentribyte diff --git a/lua/khal_endar/init.lua b/lua/khal_endar/init.lua new file mode 100644 index 0000000..01d6870 --- /dev/null +++ b/lua/khal_endar/init.lua @@ -0,0 +1,90 @@ +local M = {} +M.ui = require("khal_endar.ui") +M.utils = require("khal_endar.utils") +local date_funcs = { + ["week"] = M.utils.calculate_week_dates, + ["month"] = M.utils.calculate_month_dates, + ["quarter"] = M.utils.calculate_quarter_dates, + ["year"] = M.utils.calculate_year_dates, +} +function M.execute_command(command, return_data, print_output) + if not return_data or return_data == nil then + command = command .. " 2>&1" + end + local handle = io.popen(command) + local result = handle:read("*a") + if print_output then + print(result) + end + local _, status, code = handle:close() + return code, result +end + +function M.show(type, args) + local start_date, end_date = nil, nil + if #args == 0 then + -- start_date, end_date = M.utils.calculate_relative_datetime('2w') + start_date, end_date = M.utils.calculate_week_dates() + elseif #args == 1 then + if M.utils.table_contains(args[1], { "week", "month", "quarter", "year" }) then + start_date, end_date = date_funcs[args[1]]() + else + start_date, end_date = M.utils.calculate_relative_datetime(args[1]) + end + else + start_date, end_date = args[1], args[2] + end + local _, cal = M.execute_command("khal --color " .. type .. " " .. start_date .. " " .. end_date, true) + local contents = vim.split(cal, "\n") + M.ui.trigger_hover(contents, "List of events") +end + +function M.run(args) + M.execute_command("khal " .. table.concat(args, " "), false, true) +end + +function M.interact() + local Popup = require("nui.popup") + local event = require("nui.utils.autocmd").event + + local popup = Popup({ + enter = true, + focusable = true, + border = { + style = "rounded", + }, + + position = "50%", + size = { + width = "80%", + height = "60%", + }, + }) + + -- mount/open the component + popup:mount() + vim.cmd("term ikhal") + vim.cmd("startinsert") + popup:on(event.TermClose, function() + popup:unmount() + end) + +end + +function M.setup() + vim.api.nvim_create_user_command("KLendar", function(args) + M.show("calendar", args.fargs) + end, { nargs = "*" }) + vim.api.nvim_create_user_command("KList", function(args) + M.show("list", args.fargs) + end, { nargs = "*" }) + vim.api.nvim_create_user_command("KLInteract", function(args) + M.interact() + end, {}) + vim.api.nvim_create_user_command("KLRun", function(args) + M.run(args.fargs) + end, { nargs = "*" }) + +end + +return M diff --git a/lua/khal_endar/ui.lua b/lua/khal_endar/ui.lua new file mode 100644 index 0000000..fa1f694 --- /dev/null +++ b/lua/khal_endar/ui.lua @@ -0,0 +1,47 @@ +local M = {} + +function M.trigger_hover(contents, title) + if title == nil then + title = "" + end + local Popup = require("nui.popup") + local event = require("nui.utils.autocmd").event + local autocmd = require("nui.utils.autocmd") + local max_width = 0 + for _, content in ipairs(contents) do + max_width = math.max(max_width, #content) + end + local popup = Popup({ + enter = false, + focusable = true, + border = { + style = "rounded", + text = { + top = title, + top_align = "center", + }, + }, + relative = "cursor", + position = 0, + size = { + width = max_width + 1, + height = #contents, + }, + }) + + -- mount/open the component + local bufnr = vim.api.nvim_get_current_buf() + autocmd.buf.define(bufnr, { event.CursorMoved, event.BufWinLeave }, function() + popup:unmount() + end, { once = true }) + popup:mount() + + vim.api.nvim_buf_set_lines(popup.bufnr, 0, 1, false, contents) + vim.api.nvim_buf_set_keymap(popup.bufnr, "n", "q", "q", { silent = true }) + vim.api.nvim_buf_call(popup.bufnr, function() + vim.cmd("ColorHighlight") + end) + return popup +end + +return M diff --git a/lua/khal_endar/utils.lua b/lua/khal_endar/utils.lua new file mode 100644 index 0000000..c4859b8 --- /dev/null +++ b/lua/khal_endar/utils.lua @@ -0,0 +1,201 @@ +local M = {} + +function M.table_contains(str, tbl) + for _, value in ipairs(tbl) do + if value == str then + return true + end + end + return false +end + +-- Function to check if string_1 is a starting substring of string_2 +function M.starts_with(string, substring) + -- Get the substring from string_2 that has the same length as string_1 + local substring_from_string = string.sub(string, 1, #substring) + + -- Compare the extracted substring with string_1 + if substring_from_string == substring then + return true + else + return false + end +end + +-- Function to calculate start_date and end_date of the current week +function M.calculate_week_dates() + -- Get the current time in seconds since the epoch + local current_time = os.time() + + -- Get the current date + local current_date = os.date("*t", current_time) + + -- Calculate the day of the week where Monday is 1 and Sunday is 7 + local weekday = current_date.wday + -- Calculate the number of days to subtract to get to the start of the week (Monday) + local days_to_subtract = weekday - 2 -- We want Monday to be day 1 (subtract 2 for Sunday) + -- Calculate the start date of the week (Monday) + local start_date = os.date("*t", current_time - days_to_subtract * 86400) + start_date.hour = 0 + start_date.min = 0 + start_date.sec = 0 + -- Calculate the end date of the week (Sunday) + local end_date = os.date("*t", current_time + (7 - weekday + 1) * 86400) + end_date.hour = 23 + end_date.min = 59 + end_date.sec = 59 + -- Return the start_date and end_date + return os.date("%Y-%m-%d", os.time(start_date)), os.date("%Y-%m-%d", os.time(end_date)) +end + +function M.calculate_month_dates() + -- Get the current time in seconds since the epoch + local current_time = os.time() + + -- Get the current date + local current_date = os.date("*t", current_time) + + -- Set the day of the current date to 1 to get the start of the month + current_date.day = 1 + + -- Calculate the start_date of the month + local start_date = current_date + start_date.hour = 0 + start_date.min = 0 + start_date.sec = 0 + + -- Calculate the end_date of the month + local next_month = start_date.month + 1 + local next_month_year = start_date.year + if next_month > 12 then + next_month = 1 + next_month_year = next_month_year + 1 + end + local end_date = { + year = next_month_year, + month = next_month, + day = 1, + hour = 0, + min = 0, + sec = 0 + } + end_date = os.date("*t", os.time(end_date) - 1) -- Subtract 1 second to get last day of current month + + -- Return the start_date and end_date + return os.date("%Y-%m-%d", os.time(start_date)), os.date("%Y-%m-%d", os.time(end_date)) + +end + +function M.calculate_quarter_dates() + -- Get the current time in seconds since the epoch + local current_time = os.time() + + -- Get the current date + local current_date = os.date("*t", current_time) + + -- Determine the current quarter + local quarter = math.ceil(current_date.month / 3) + + -- Calculate the start_date of the quarter + local start_date = { + year = current_date.year, + month = (quarter - 1) * 3 + 1, + day = 1, + hour = 0, + min = 0, + sec = 0 + } + + -- Calculate the end_date of the quarter + local end_date = { + year = current_date.year, + month = quarter * 3 + 1, + day = 1, + hour = 0, + min = 0, + sec = 0 + } + end_date = os.date("*t", os.time(end_date) - 86400) -- Subtract 1 day to get last day of previous month + + -- Return the start_date and end_date + return os.date("%Y-%m-%d", os.time(start_date)), os.date("%Y-%m-%d", os.time(end_date)) + +end + +-- Function to calculate start_date and end_date of the current year +function M.calculate_year_dates() + -- Get the current time in seconds since the epoch + local current_time = os.time() + + -- Get the current date + local current_date = os.date("*t", current_time) + + -- Calculate the start_date of the year (January 1st of the current year) + local start_date = { + year = current_date.year, + month = 1, + day = 1, + hour = 0, + min = 0, + sec = 0 + } + + -- Calculate the end_date of the year (December 31st of the current year) + local end_date = { + year = current_date.year, + month = 12, + day = 31, + hour = 23, + min = 59, + sec = 59 + } + + -- Return the start_date and end_date + return os.date("%Y-%m-%d", os.time(start_date)), os.date("%Y-%m-%d", os.time(end_date)) + +end + +function M.calculate_relative_datetime(interval) + -- Parse the interval string to extract the numeric value and unit + local numeric_value, unit = interval:match("(%d+)(%a+)") + if not numeric_value or not unit then + return nil -- Invalid interval format + end + + -- Convert numeric value to integer + local numeric = tonumber(numeric_value) + if not numeric then + return nil -- Invalid numeric value + end + + -- Get current time in seconds since epoch + local now = os.time() + + -- Define time units in seconds + local unit_seconds = { + ["secconds"] = 1, + ["minutes"] = 60, + ["hours"] = 3600, + ["days"] = 86400, + ["weeks"] = 604800, -- 7 days in seconds + ["months"] = 2592000, -- 30 days in seconds + ["years"] = 31536000, -- 365 days in seconds + } + for key, _ in pairs(unit_seconds) do + if M.starts_with(key, unit) then + unit = key + break + end + end + -- Calculate the relative time in seconds + local interval_seconds = unit_seconds[unit] + if not interval_seconds then + return nil -- Invalid time unit + end + + local relative_time = now + numeric * interval_seconds + return 'today', os.date("%Y-%m-%d", relative_time) +end + + +return M