From 78a7d1d3d7f87155edb315d18939c0967fb6e206 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 21 Jan 2025 23:04:23 +0100 Subject: [PATCH] refactor(date): Refactor date object --- lua/orgmode/agenda/types/agenda.lua | 4 +- lua/orgmode/agenda/types/tags.lua | 5 +- lua/orgmode/capture/template/datetree.lua | 1 - lua/orgmode/capture/template/init.lua | 22 +- lua/orgmode/files/elements/search.lua | 6 +- lua/orgmode/files/headline.lua | 2 +- lua/orgmode/notifications/init.lua | 4 +- lua/orgmode/objects/date.lua | 839 +++++++++++----------- lua/orgmode/org/mappings.lua | 5 +- tests/plenary/object/date_spec.lua | 50 +- tests/plenary/ui/mappings/todo_spec.lua | 2 +- 11 files changed, 474 insertions(+), 466 deletions(-) diff --git a/lua/orgmode/agenda/types/agenda.lua b/lua/orgmode/agenda/types/agenda.lua index 70c338007..ac3556bce 100644 --- a/lua/orgmode/agenda/types/agenda.lua +++ b/lua/orgmode/agenda/types/agenda.lua @@ -71,7 +71,7 @@ function OrgAgendaType:new(opts) category_filter = opts.category_filter and AgendaFilter:new({ types = { 'categories' } }) :parse(opts.category_filter, true) or nil, span = opts.span or config:get_agenda_span(), - from = opts.from or Date.now():start_of('day'), + from = opts.from or Date.today(), to = nil, clock_report = nil, show_clock_report = opts.show_clock_report or false, @@ -160,7 +160,7 @@ function OrgAgendaType:goto_date(date) end function OrgAgendaType:reset() - return self:goto_date(Date.now():start_of('day')) + return self:goto_date(Date.today()) end ---@return OrgAgendaLine[] diff --git a/lua/orgmode/agenda/types/tags.lua b/lua/orgmode/agenda/types/tags.lua index 5787c8825..edcd9f299 100644 --- a/lua/orgmode/agenda/types/tags.lua +++ b/lua/orgmode/agenda/types/tags.lua @@ -2,7 +2,6 @@ local Date = require('orgmode.objects.date') local config = require('orgmode.config') local utils = require('orgmode.utils') -local validator = require('orgmode.utils.validator') local Search = require('orgmode.files.elements.search') local OrgAgendaTodosType = require('orgmode.agenda.types.todo') @@ -58,11 +57,11 @@ function OrgAgendaTagsType:get_file_headlines(file) return false end if self.todo_ignore_deadlines == 'near' then - local diff = deadline_date:diff(Date.now()) + local diff = deadline_date:diff(Date.now(), 'day') return diff > config.org_deadline_warning_days end if self.todo_ignore_deadlines == 'far' then - local diff = deadline_date:diff(Date.now()) + local diff = deadline_date:diff(Date.now(), 'day') return diff <= config.org_deadline_warning_days end if self.todo_ignore_deadlines == 'past' then diff --git a/lua/orgmode/capture/template/datetree.lua b/lua/orgmode/capture/template/datetree.lua index 3249b5af7..ec7a659b0 100644 --- a/lua/orgmode/capture/template/datetree.lua +++ b/lua/orgmode/capture/template/datetree.lua @@ -1,5 +1,4 @@ local utils = require('orgmode.utils') -local Date = require('orgmode.objects.date') ---@class OrgDatetree ---@field files OrgFiles diff --git a/lua/orgmode/capture/template/init.lua b/lua/orgmode/capture/template/init.lua index 121f35c52..acee3f12e 100644 --- a/lua/orgmode/capture/template/init.lua +++ b/lua/orgmode/capture/template/init.lua @@ -23,51 +23,51 @@ local expansions = { end, ['%%%^t'] = function() return Calendar.new({ date = Date.today() }):open():next(function(date) - return date and string.format('<%s>', date:to_string()) or nil + return date and date:to_wrapped_string(true) or nil end) end, ['%%%^%{([^%}]*)%}t'] = function(title) return Calendar.new({ date = Date.today(), title = title }):open():next(function(date) - return date and string.format('<%s>', date:to_string()) or nil + return date and date:to_wrapped_string(true) or nil end) end, ['%%T'] = function() - return string.format('<%s>', Date.now():to_string()) + return Date.now():to_wrapped_string(true) end, ['%%%^T'] = function() return Calendar.new({ date = Date.now() }):open():next(function(date) - return date and string.format('<%s>', date:to_string()) or nil + return date and date:to_wrapped_string(true) or nil end) end, ['%%%^%{([^%}]*)%}T'] = function(title) return Calendar.new({ date = Date.now(), title = title }):open():next(function(date) - return date and string.format('<%s>', date:to_string()) or nil + return date and date:to_wrapped_string(true) or nil end) end, ['%%u'] = function() - return string.format('[%s]', Date.today():to_string()) + return Date.today():to_wrapped_string(false) end, ['%%%^u'] = function() return Calendar.new({ date = Date.today() }):open():next(function(date) - return date and string.format('[%s]', date:to_string()) or nil + return date and date:to_wrapped_string(false) or nil end) end, ['%%%^%{([^%}]*)%}u'] = function(title) return Calendar.new({ date = Date.today(), title = title }):open():next(function(date) - return date and string.format('[%s]', date:to_string()) or nil + return date and date:to_wrapped_string(false) or nil end) end, ['%%U'] = function() - return string.format('[%s]', Date.now():to_string()) + return Date.now():to_wrapped_string(false) end, ['%%%^U'] = function() return Calendar.new({ date = Date.now() }):open():next(function(date) - return date and string.format('[%s]', date:to_string()) or nil + return date and date:to_wrapped_string(false) or nil end) end, ['%%%^%{([^%}]*)%}U'] = function(title) return Calendar.new({ date = Date.now(), title = title }):open():next(function(date) - return date and string.format('[%s]', date:to_string()) or nil + return date and date:to_wrapped_string(false) or nil end) end, ['%%a'] = function() diff --git a/lua/orgmode/files/elements/search.lua b/lua/orgmode/files/elements/search.lua index 64268f337..7ae041d55 100644 --- a/lua/orgmode/files/elements/search.lua +++ b/lua/orgmode/files/elements/search.lua @@ -396,14 +396,14 @@ function PropertyMatch:parse(input) ---@type string?, OrgDate? local date_content, date_value if date_str == '' then - date_value = Date.today():start_of('day') + date_value = Date.today() elseif date_str == '' then - date_value = Date.tomorrow():start_of('day') + date_value = Date.tomorrow() else -- Parse relative formats (e.g. <+1d>) as well as absolute date_content = date_str:match('^<([%+%-]%d+[dmyhwM])>$') if date_content then - date_value = Date.today():start_of('day') + date_value = Date.today() date_value = date_value:adjust(date_content) else date_content = date_str:match('^<([^>]+)>$') diff --git a/lua/orgmode/files/headline.lua b/lua/orgmode/files/headline.lua index d68f99299..1a3fe6ce3 100644 --- a/lua/orgmode/files/headline.lua +++ b/lua/orgmode/files/headline.lua @@ -775,7 +775,7 @@ function Headline:get_valid_dates_for_agenda() for _, date in ipairs(self:get_all_dates()) do if date.active and not date:is_closed() and not date:is_obsolete_range_end() then table.insert(dates, date) - if not date:is_none() and date.related_date_range then + if not date:is_none() and date.related_date then local new_date = date:clone({ type = 'NONE' }) table.insert(dates, new_date) end diff --git a/lua/orgmode/notifications/init.lua b/lua/orgmode/notifications/init.lua index 30aca040d..6a8c9b1ae 100644 --- a/lua/orgmode/notifications/init.lua +++ b/lua/orgmode/notifications/init.lua @@ -22,7 +22,7 @@ end function Notifications:start_timer() self:stop_timer() self.timer = vim.loop.new_timer() - self:notify(Date.now():start_of('minute')) + self:notify(Date.now()) self.timer:start( (60 - os.date('%S')) * 1000, 60000, @@ -62,7 +62,7 @@ function Notifications:notify(time) end function Notifications:cron() - local tasks = self:get_tasks(Date.now():start_of('minute')) + local tasks = self:get_tasks(Date.now()) if type(config.notifications.cron_notifier) == 'function' then config.notifications.cron_notifier(tasks) else diff --git a/lua/orgmode/objects/date.lua b/lua/orgmode/objects/date.lua index 9c7ea4bc8..ac93d63dd 100644 --- a/lua/orgmode/objects/date.lua +++ b/lua/orgmode/objects/date.lua @@ -1,5 +1,3 @@ --- TODO --- Support diary format and format without short date name ---@type OrgDateSpan local spans = { d = 'day', m = 'month', y = 'year', h = 'hour', w = 'week', M = 'min' } local config = require('orgmode.config') @@ -9,179 +7,94 @@ local pattern = '([<%[])(%d%d%d%d%-%d?%d%-%d%d[^>%]]*)([>%]])' local date_format = '%Y-%m-%d' local time_format = '%H:%M' ----@alias OrgDateSpan 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year' - ----@class OrgDateOpts : osdateparam ----@field active boolean +---@alias OrgDateSpan 'hour' | 'day' | 'week' | 'month' | 'year' | string + +---@class OrgDateSetOpts +---@field day? number +---@field month? number +---@field year? number +---@field hour? number +---@field min? number +---@field date_only? boolean +---@field type? string +---@field range? OrgRange +---@field active? boolean +---@field adjustments? string[] +--- +---@class OrgDateOpts:OrgDateSetOpts +---@field timestamp_end? number +---@field is_date_range_start? boolean +---@field is_date_range_end? boolean +---@field related_date? OrgDate + +---@class OrgDate +---@field day number +---@field month number +---@field year number +---@field hour number +---@field min number ---@field date_only boolean +---@field wday number +---@field isdst boolean ---@field type string ----@field range OrgRange +---@field dayname string +---@field range? OrgRange +---@field active boolean +---@field adjustments string[] ---@field timestamp number ----@field timestamp_end number ----@field is_dst boolean +---@field timestamp_end? number ---@field is_date_range_start boolean ---@field is_date_range_end boolean ----@field related_date_range OrgDate? ----@field dayname string? ----@field adjustments string[] ----@field private is_today_date boolean? - ----@class OrgDate : OrgDateOpts -local Date = { +---@field related_date? OrgDate +local OrgDate = { + pattern = pattern, ---@type fun(this: OrgDate, other: OrgDate): boolean __eq = function(this, other) - if this.date_only ~= other.date_only then - return this:is_same(other, 'day') - end return this:is_same(other) end, ---@type fun(this: OrgDate, other: OrgDate): boolean __lt = function(this, other) - if this.date_only ~= other.date_only then - return this:is_before(other, 'day') - end return this:is_before(other) end, ---@type fun(this: OrgDate, other: OrgDate): boolean __le = function(this, other) - if this.date_only ~= other.date_only then - return this:is_same_or_before(other, 'day') - end return this:is_same_or_before(other) end, ---@type fun(this: OrgDate, other: OrgDate): boolean __gt = function(this, other) - if this.date_only ~= other.date_only then - return this:is_after(other, 'day') - end return this:is_after(other) end, ---@type fun(this: OrgDate, other: OrgDate): boolean __ge = function(this, other) - if this.date_only ~= other.date_only then - return this:is_same_or_after(other, 'day') - end return this:is_same_or_after(other) end, } +OrgDate.__index = OrgDate ----@param source table ----@param target? table ----@param include_sec? boolean ----@return OrgDateOpts -local function set_date_opts(source, target, include_sec) - target = target or {} - for _, field in ipairs({ 'year', 'month', 'day' }) do - target[field] = source[field] - end - for _, field in ipairs({ 'hour', 'min' }) do - target[field] = source[field] or 0 - end - if include_sec then - target.sec = source.sec or 0 - end - return target -end - ----@param timestamp integer +---@param timestamp number ---@param format? string ---@return osdate local function os_date(timestamp, format) return os.date(format or '*t', timestamp) --[[@as osdate]] end ----@param data OrgDateOpts ----@return OrgDate -function Date:new(data) - data = data or {} - local date_only = data.date_only or (not data.hour and not data.min) - local opts = set_date_opts(data) - opts.type = data.type or 'NONE' - opts.active = data.active or false - opts.range = data.range - opts.timestamp = os.time(opts) - opts.date_only = date_only or false - opts.dayname = os.date('%a', opts.timestamp) --[[@as string]] - opts.is_dst = os_date(opts.timestamp).isdst - opts.adjustments = data.adjustments or {} - opts.timestamp_end = data.timestamp_end - opts.is_date_range_start = data.is_date_range_start or false - opts.is_date_range_end = data.is_date_range_end or false - opts.related_date_range = data.related_date_range or nil - setmetatable(opts, self) - self.__index = self - ---@diagnostic disable-next-line:return-type-mismatch - return opts -end - ----@param time table ----@return OrgDate -function Date:from_time_table(time) - local timestamp_end = self.timestamp_end - local timestamp = self.timestamp - local range_diff = timestamp_end and timestamp_end - timestamp or 0 - timestamp = os.time(set_date_opts(time, {}, true)) - local opts = set_date_opts(os_date(timestamp)) - if time.date_only ~= nil then - opts.date_only = time.date_only - else - opts.date_only = self.date_only - end - opts.dayname = self.dayname - opts.adjustments = self.adjustments - opts.type = self.type - opts.active = self.active - opts.range = self.range - if self.timestamp_end then - opts.timestamp_end = timestamp + range_diff - end - opts.is_date_range_start = self.is_date_range_start - opts.is_date_range_end = self.is_date_range_end - opts.related_date_range = self.related_date_range - return Date:new(opts) -end - ----@param opts? table ----@return OrgDate -function Date:set(opts) - opts = opts or {} - local date = os_date(self.timestamp) - for opt, val in pairs(opts) do - date[opt] = val - end - return self:from_time_table(date) -end - ----@param opts? table ----@return OrgDate -function Date:clone(opts) - local date = Date:new(self) - for opt, val in pairs(opts or {}) do - date[opt] = val - end - return date -end - ---@param date string ----@param dayname string? ---@param time string ---@param adjustments string[] ----@param data table +---@param data OrgDateOpts ---@return OrgDate -local function parse_datetime(date, dayname, time, time_end, adjustments, data) +local function parse_datetime(date, time, time_end, adjustments, data) local date_parts = vim.split(date, '-') local time_parts = vim.split(time, ':') - ---@type OrgDate + ---@type OrgDateOpts local opts = { year = tonumber(date_parts[1]) or 0, month = tonumber(date_parts[2]) or 0, day = tonumber(date_parts[3]) or 0, hour = tonumber(time_parts[1]) or 0, min = tonumber(time_parts[2]) or 0, - wday = dayname or '', } - opts.dayname = dayname opts.adjustments = adjustments if time_end then local time_end_parts = vim.split(time_end, ':') @@ -191,77 +104,274 @@ local function parse_datetime(date, dayname, time, time_end, adjustments, data) day = tonumber(date_parts[3]) or 0, hour = tonumber(time_end_parts[1]) or 0, min = tonumber(time_end_parts[2]) or 0, - wday = dayname or '', }) end opts = vim.tbl_extend('force', opts, data or {}) - return Date:new(opts) + return OrgDate:new(opts) end ---@param date string ----@param dayname string? ---@param adjustments string[] ----@param data table +---@param data OrgDateOpts ---@return OrgDate -local function parse_date(date, dayname, adjustments, data) +local function parse_date(date, adjustments, data) local date_parts = vim.split(date, '-') + ---@type OrgDateOpts local opts = { - year = tonumber(date_parts[1]), - month = tonumber(date_parts[2]), - day = tonumber(date_parts[3]), + year = tonumber(date_parts[1]) or 0, + month = tonumber(date_parts[2]) or 0, + day = tonumber(date_parts[3]) or 0, } opts.adjustments = adjustments - opts.dayname = dayname opts = vim.tbl_extend('force', opts, data or {}) - return Date:new(opts) + return OrgDate:new(opts) end ----@param data? table +---@param line string +---@param lnum number +---@param open string +---@param datetime string +---@param close string +---@param last_match? OrgDate +---@param type? string +---@return OrgDate | nil +local function from_match(line, lnum, open, datetime, close, last_match, type) + local search_from = last_match ~= nil and last_match.range.end_col or 0 + local from, to = line:find(vim.pesc(open .. datetime .. close), search_from) + local is_date_range_end = last_match and last_match.is_date_range_start and line:sub(from - 2, from - 1) == '--' + local opts = { + type = type, + active = open == '<', + range = Range:new({ start_line = lnum, end_line = lnum, start_col = from, end_col = to }), + is_date_range_start = line:sub(to + 1, to + 2) == '--', + } + local parsed_date = OrgDate.from_string(vim.trim(datetime), opts) + if is_date_range_end then + parsed_date.is_date_range_end = true + parsed_date.related_date = last_match + last_match.related_date = parsed_date + end + + return parsed_date +end + +---@param opts OrgDateOpts +---@return OrgDate +function OrgDate:new(opts) + local data = {} + data.date_only = opts.date_only or (not opts.hour and not opts.min) + data.timestamp = os.time({ + year = opts.year, + month = opts.month, + day = opts.day, + hour = opts.hour or 0, + min = opts.min or 0, + }) + local date_info = os_date(data.timestamp) + data.day = date_info.day + data.month = date_info.month + data.year = date_info.year + data.hour = date_info.hour + data.min = date_info.min + data.isdst = date_info.isdst + data.wday = date_info.wday + + data.active = opts.active or false + data.type = opts.type or 'NONE' + data.range = opts.range + data.dayname = os_date(data.timestamp, '%a') --[[@as string]] + data.adjustments = opts.adjustments or {} + data.timestamp_end = opts.timestamp_end + data.is_date_range_start = opts.is_date_range_start or false + data.is_date_range_end = opts.is_date_range_end or false + data.related_date = opts.related_date or nil + + return setmetatable(data, self) +end + +---@param opts OrgDateSetOpts ---@return OrgDate -local function today(data) - local date = os_date(os.time()) - local opts = vim.tbl_deep_extend('force', date, data or {}) --[[@as OrgDateOpts]] +function OrgDate:set(opts) + local data = { + day = self.day, + month = self.month, + year = self.year, + hour = self.hour, + min = self.min, + date_only = self.date_only, + type = self.type, + range = self.range, + active = self.active, + adjustments = self.adjustments, + is_date_range_start = self.is_date_range_start, + is_date_range_end = self.is_date_range_end, + related_date = self.related_date, + } + + if type(opts.date_only) == 'boolean' then + data.date_only = opts.date_only + end + + for key, value in pairs(opts) do + data[key] = value + end + + if self.timestamp_end then + local range_diff = self.timestamp_end - self.timestamp + local timestamp = os.time(data) + data.timestamp_end = timestamp + range_diff + end + + return OrgDate:new(data) +end + +---@param opts? OrgDateOpts +function OrgDate:clone(opts) + return self:set(opts or {}) +end + +---Return todays date without the time +---@param opts? OrgDateOpts +---@return OrgDate +function OrgDate.today(opts) + opts = opts or {} opts.date_only = true - return Date:new(opts) + return OrgDate.from_timestamp(os.time(), opts) end +---Return current date and time +---@param opts? OrgDateOpts ---@return OrgDate -local function tomorrow() - local today_date = today() - return today_date:adjust('+1d') +function OrgDate.now(opts) + return OrgDate.from_timestamp(os.time(), opts) end ----@param data? table +---Return tomorrows date without the time +---@param opts? OrgDateOpts ---@return OrgDate -local function now(data) - local date = os_date(os.time()) - local opts = vim.tbl_deep_extend('force', date, data or {}) --[[@as OrgDateOpts]] - return Date:new(opts) +function OrgDate.tomorrow(opts) + local today = OrgDate.today(opts) + return today:adjust('+1d') +end + +---@param timestamp number +---@param opts? OrgDateOpts +---@return OrgDate +function OrgDate.from_timestamp(timestamp, opts) + local date = os_date(timestamp) + local data = { + day = date.day, + month = date.month, + year = date.year, + } + if not opts or not opts.date_only then + data.hour = date.hour + data.min = date.min + end + if opts then + data = vim.tbl_extend('force', opts, data) + end + return OrgDate:new(data) +end + +---Accept org format date, for example <2025-22-01 Wed> or range <2025-22-01 Wed>--<2025-24-01 Fri> +---@param datestr string +---@param opts? OrgDateOpts +---@return OrgDate[] +function OrgDate.from_org_date(datestr, opts) + opts = opts or {} + local from_open, from, from_close, delimiter, to_open, to, to_close = datestr:match(pattern .. '(%-%-)' .. pattern) + if not delimiter then + if not OrgDate.is_valid_date_string(datestr:sub(2, -2)) then + return {} + end + local is_active = datestr:sub(1, 1) == '<' and datestr:sub(-1) == '>' + local dateval = datestr:gsub('^[%[<]', ''):gsub('[%]>]', '') + opts = opts or {} + opts.active = is_active + return { OrgDate.from_string(dateval, opts) } + end + + local line = opts.range.start_line + local start_date = OrgDate.from_string( + from, + vim.tbl_extend('force', opts or {}, { + active = from_open == '<' and from_close == '>', + is_date_range_start = true, + range = Range:new({ + start_line = line, + end_line = line, + start_col = opts.range.start_col, + end_col = opts.range.start_col + (from_open .. from .. from_close):len() - 1, + }), + }) + ) + + local end_date = OrgDate.from_string( + to, + vim.tbl_extend('force', opts or {}, { + active = to_open == '<' and to_close == '>', + is_date_range_end = true, + range = Range:new({ + start_line = line, + end_line = line, + start_col = start_date and start_date.range.end_col + 3, + end_col = opts.range.end_col, + }), + related_date = start_date, + }) + ) + if end_date then + start_date.related_date = end_date + end + + return { start_date, end_date } end ---@param datestr string ----@return string|nil -local function is_valid_date(datestr) +---@return boolean +function OrgDate.is_valid_date_string(datestr) return datestr:match('^%d%d%d%d%-%d%d%-%d%d%s+') or datestr:match('^%d%d%d%d%-%d%d%-%d%d$') end +---@param value any +---@return boolean +function OrgDate.is_date_instance(value) + return getmetatable(value) == OrgDate +end + +---@param line string +---@param lnum number +---@return OrgDate[] +function OrgDate.parse_all_from_line(line, lnum) + local is_comment = line:match('^%s*#[^%+]') + if is_comment then + return {} + end + local dates = {} + for open, datetime, close in line:gmatch(pattern) do + local parsed_date = from_match(line, lnum, open, datetime, close, dates[#dates]) + if parsed_date then + table.insert(dates, parsed_date) + end + end + return dates +end + ---@param datestr string ----@param opts table? ----@return OrgDate? -local function from_string(datestr, opts) - if not is_valid_date(datestr) then +---@param opts? OrgDateOpts +---@return OrgDate | nil +function OrgDate.from_string(datestr, opts) + if not OrgDate.is_valid_date_string(datestr) then return nil end local parts = vim.split(datestr, '%s+') local date = table.remove(parts, 1) - local dayname = nil local time = nil local time_end = nil local adjustments = {} for _, part in ipairs(parts) do - if part:match('%a%a%a') then - dayname = part - elseif part:match('%d?%d:%d%d%-%d?%d:%d%d') then + if part:match('%d?%d:%d%d%-%d?%d:%d%d') then local times = vim.split(part, '-') time = times[1] time_end = times[2] @@ -273,15 +383,14 @@ local function from_string(datestr, opts) end if time then - return parse_datetime(date, dayname, time, time_end, adjustments, opts or {}) + return parse_datetime(date, time, time_end, adjustments, opts or {}) end - return parse_date(date, dayname, adjustments, opts or {}) + return parse_date(date, adjustments, opts or {}) end ---- @param datestr string ---- @return table[] -local function parse_parts(datestr) +--- @return { type: 'date' | 'dayname' | 'time' | 'time_range' | 'adjustment', value: string, from: number, to: number }[] +function OrgDate:parse_parts() local result = {} local counter = 1 local patterns = { @@ -291,7 +400,7 @@ local function parse_parts(datestr) { type = 'time_range', rgx = '^%d?%d:%d%d%-%d?%d:%d%d$' }, { type = 'adjustment', rgx = '^[%.%+%-]+%d+[hdwmy]?$' }, } - for space, item in string.gmatch(datestr, '(%s*)(%S+)') do + for space, item in string.gmatch(self:to_string(), '(%s*)(%S+)') do local from = counter + space:len() for _, dt_pattern in ipairs(patterns) do if item:match(dt_pattern.rgx) then @@ -308,52 +417,8 @@ local function parse_parts(datestr) return result end -local function from_org_date(datestr, opts) - local from_open, from, from_close, delimiter, to_open, to, to_close = datestr:match(pattern .. '(%-%-)' .. pattern) - if not delimiter then - if not is_valid_date(datestr:sub(2, -2)) then - return {} - end - local is_active = datestr:sub(1, 1) == '<' and datestr:sub(-1) == '>' - local dateval = datestr:gsub('^[%[<]', ''):gsub('[%]>]', '') - return { from_string(dateval, vim.tbl_extend('force', opts or {}, { active = is_active })) } - end - local line = opts.range.start_line - local start_date = from_string( - from, - vim.tbl_extend('force', opts or {}, { - active = from_open == '<' and from_close == '>', - is_date_range_start = true, - range = Range:new({ - start_line = line, - end_line = line, - start_col = opts.range.start_col, - end_col = opts.range.start_col + (from_open .. from .. from_close):len() - 1, - }), - }) - ) - - local end_date = from_string( - to, - vim.tbl_extend('force', opts or {}, { - active = to_open == '<' and to_close == '>', - is_date_range_end = true, - range = Range:new({ - start_line = line, - end_line = line, - start_col = start_date and start_date.range.end_col + 3, - end_col = opts.range.end_col, - }), - related_date_range = start_date, - }) - ) - start_date.related_date_range = end_date - - return { start_date, end_date } -end - ---@return string -function Date:to_string() +function OrgDate:to_string() local format = date_format if self.dayname then format = format .. ' %a' @@ -373,7 +438,7 @@ function Date:to_string() end ---@return string -function Date:to_date_string() +function OrgDate:to_date_string() local old_date_only = self.date_only self.date_only = true local result = self:to_string() @@ -381,9 +446,9 @@ function Date:to_date_string() return result end ----@param active boolean|nil +---@param active boolean | nil ---@return string -function Date:to_wrapped_string(active) +function OrgDate:to_wrapped_string(active) if type(active) ~= 'boolean' then active = self.active end @@ -393,8 +458,14 @@ function Date:to_wrapped_string(active) return string.format('%s%s%s', open, date, close) end +---@param format string ---@return string -function Date:format_time() +function OrgDate:format(format) + return tostring(os.date(format, self.timestamp)) +end + +---@return string +function OrgDate:format_time() if not self:has_time() then return '' end @@ -405,9 +476,14 @@ function Date:format_time() return t end +---@return boolean +function OrgDate:has_time() + return not self.date_only +end + ---@param value string ---@return OrgDate -function Date:adjust(value) +function OrgDate:adjust(value) local adjustment = self:_parse_adjustment(value) local modifier = { [adjustment.span] = adjustment.amount } if adjustment.is_negative then @@ -418,11 +494,11 @@ end ---@param value string ---@return OrgDate -function Date:adjust_end_time(value) +function OrgDate:adjust_end_time(value) if not self.timestamp_end then return self end - local time_end = Date:from_time_table(os_date(self.timestamp_end)) + local time_end = OrgDate.from_timestamp(self.timestamp_end) time_end = time_end:adjust(value) self.timestamp_end = time_end.timestamp return self @@ -430,7 +506,7 @@ end ---@param value string ---@return table -function Date:_parse_adjustment(value) +function OrgDate:_parse_adjustment(value) local operation, amount, span = value:match('^([%+%-])(%d+)([hdwmyM]?)') if not operation or not amount then return { span = 'day', amount = 0 } @@ -445,13 +521,14 @@ function Date:_parse_adjustment(value) } end -function Date:without_adjustments() +---@return OrgDate +function OrgDate:without_adjustments() return self:clone({ adjustments = {} }) end ----@param span OrgDateSpan | string +---@param span OrgDateSpan ---@return OrgDate -function Date:start_of(span) +function OrgDate:start_of(span) if #span == 1 then span = spans[span] end @@ -460,7 +537,6 @@ function Date:start_of(span) month = { day = 1, hour = 0, min = 0 }, year = { month = 1, day = 1, hour = 0, min = 0 }, hour = { min = 0 }, - minute = { sec = 0 }, } if opts[span] then return self:set(opts[span]) @@ -468,10 +544,8 @@ function Date:start_of(span) if span == 'week' then local this = self - local date = os_date(self.timestamp) - while date.wday ~= config:get_week_start_day_number() do + while this.wday ~= config:get_week_start_day_number() do this = this:adjust('-1d') - date = os_date(this.timestamp) end return this:set(opts.day) end @@ -479,9 +553,9 @@ function Date:start_of(span) return self end ----@param span string +---@param span OrgDateSpan ---@return OrgDate -function Date:end_of(span) +function OrgDate:end_of(span) if #span == 1 then span = spans[span] end @@ -497,44 +571,39 @@ function Date:end_of(span) if span == 'week' then local this = self - local date = os_date(self.timestamp) - while date.wday ~= config:get_week_end_day_number() do + while this.wday ~= config:get_week_end_day_number() do this = this:adjust('+1d') - date = os_date(this.timestamp) end return this:set(opts.day) end if span == 'month' then local date = os_date(self.timestamp) - return self:set({ day = Date._days_of_month(date) }):end_of('day') + return self:set({ day = OrgDate._days_of_month(date) }):end_of('day') end return self end -function Date:last_day_of_month() - return self:set({ day = Date._days_of_month(os_date(self.timestamp)) }) +---@return OrgDate +function OrgDate:last_day_of_month() + return self:set({ day = OrgDate._days_of_month(os_date(self.timestamp)) }) end ---@return number -function Date:get_isoweekday() - ---@type table - ---@diagnostic disable-next-line: assign-type-mismatch - local date = os_date(self.timestamp) - return utils.convert_to_isoweekday(date.wday) +function OrgDate:get_isoweekday() + return utils.convert_to_isoweekday(self.wday) end ---@return number -function Date:get_weekday() - local date = os_date(self.timestamp) - return tonumber(date.wday) or 0 +function OrgDate:get_weekday() + return tonumber(self.wday) or 0 end ---@param isoweekday number ---@param future? boolean ---@return OrgDate -function Date:set_isoweekday(isoweekday, future) +function OrgDate:set_isoweekday(isoweekday, future) local current_isoweekday = self:get_isoweekday() if isoweekday <= current_isoweekday then return self:subtract({ day = current_isoweekday - isoweekday }) @@ -545,9 +614,9 @@ function Date:set_isoweekday(isoweekday, future) return self:subtract({ week = 1 }):add({ day = isoweekday - current_isoweekday }) end ----@param opts table +---@param opts OrgDateSetOpts ---@return OrgDate -function Date:add(opts) +function OrgDate:add(opts) opts = opts or {} ---@type table ---@diagnostic disable-next-line: assign-type-mismatch @@ -560,19 +629,19 @@ function Date:add(opts) date[opt] = date[opt] + val end if opts['month'] then - date['day'] = math.min(date['day'], Date._days_of_month(date)) + date['day'] = math.min(date['day'], OrgDate._days_of_month(date)) end - return self:from_time_table(date) + return self:set(date) end ----@param date table +---@param date osdate ---@return number -function Date._days_of_month(date) +function OrgDate._days_of_month(date) local days_of = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } local month = date.month if month == 2 then - return Date._days_of_february(date.year) + return OrgDate._days_of_february(date.year) end if month >= 1 and month <= 12 then @@ -593,17 +662,19 @@ function Date._days_of_month(date) return days_of[month] end -function Date._days_of_february(year) - return Date._is_leap_year(year) and 29 or 28 +---@return number +function OrgDate._days_of_february(year) + return OrgDate._is_leap_year(year) and 29 or 28 end -function Date._is_leap_year(year) +---@return boolean +function OrgDate._is_leap_year(year) return year % 400 == 0 or (year % 100 ~= 0 and year % 4 == 0) end ----@param opts table +---@param opts OrgDateSetOpts ---@return OrgDate -function Date:subtract(opts) +function OrgDate:subtract(opts) opts = opts or {} for opt, val in pairs(opts) do opts[opt] = -val @@ -611,109 +682,117 @@ function Date:subtract(opts) return self:add(opts) end +---@return number +function OrgDate:get_comparable_timestamp() + if self.date_only then + return self:start_of('day').timestamp + end + return self.timestamp +end + ---@param date OrgDate ----@param span? string +---@param span? OrgDateSpan ---@return boolean -function Date:is_same(date, span) - if not span then - return self.timestamp == date.timestamp +function OrgDate:is_same(date, span) + if span then + return self:start_of(span).timestamp == date:start_of(span).timestamp end - return self:start_of(span).timestamp == date:start_of(span).timestamp + return self:get_comparable_timestamp() == date:get_comparable_timestamp() end ---@param from OrgDate ---@param to OrgDate ----@param span string +---@param span OrgDateSpan ---@return boolean -function Date:is_between(from, to, span) +function OrgDate:is_between(from, to, span) local f = from local t = to if span then f = from:start_of(span) t = to:end_of(span) end - return self.timestamp >= f.timestamp and self.timestamp <= t.timestamp + local self_ts = self:get_comparable_timestamp() + local from_ts = f:get_comparable_timestamp() + local to_ts = t:get_comparable_timestamp() + return self_ts >= from_ts and self_ts <= to_ts end ---@param date OrgDate ----@param span? string +---@param span? OrgDateSpan ---@return boolean -function Date:is_before(date, span) +function OrgDate:is_before(date, span) return not self:is_same_or_after(date, span) end ---@param date OrgDate ----@param span? string +---@param span? OrgDateSpan ---@return boolean -function Date:is_same_or_before(date, span) +function OrgDate:is_same_or_before(date, span) local d = date local s = self if span then d = date:start_of(span) s = self:start_of(span) end - return s.timestamp <= d.timestamp + return s:get_comparable_timestamp() <= d:get_comparable_timestamp() end ---@param date OrgDate ----@param span? string +---@param span? OrgDateSpan ---@return boolean -function Date:is_after(date, span) +function OrgDate:is_after(date, span) return not self:is_same_or_before(date, span) end ---@param date OrgDate ----@param span string? +---@param span OrgDateSpan? ---@return boolean -function Date:is_same_or_after(date, span) +function OrgDate:is_same_or_after(date, span) local d = date local s = self if span then d = date:start_of(span) s = self:start_of(span) end - return s.timestamp >= d.timestamp + return s:get_comparable_timestamp() >= d:get_comparable_timestamp() end ---@return boolean -function Date:is_today() +function OrgDate:is_today() if self.is_today_date == nil then - local date = now() + local date = OrgDate.now() self.is_today_date = self:is_same_day(date) end return self.is_today_date end ---@return boolean -function Date:is_same_day(date) +function OrgDate:is_same_day(date) return date and date.year == self.year and date.month == self.month and date.day == self.day end ---@return boolean -function Date:is_obsolete_range_end() - return self.is_date_range_end and self.related_date_range:is_same(self, 'day') +function OrgDate:is_obsolete_range_end() + return self.is_date_range_end and self.related_date:is_same(self, 'day') end ---@return boolean -function Date:has_date_range_end() - return self.related_date_range ~= nil and self.is_date_range_start -end - -function Date:has_time() - return not self.date_only +function OrgDate:has_date_range_end() + return self.related_date ~= nil and self.is_date_range_start end ---@return boolean -function Date:has_time_range() +function OrgDate:has_time_range() return self.timestamp_end ~= nil end ----@return OrgDate|nil -function Date:get_date_range_end() - return self:has_date_range_end() and self.related_date_range or nil +---@return OrgDate | nil +function OrgDate:get_date_range_end() + return self:has_date_range_end() and self.related_date or nil end -function Date:get_type_sort_value() +---@return number +function OrgDate:get_type_sort_value() local types = { DEADLINE = 1, SCHEDULED = 2, @@ -722,24 +801,23 @@ function Date:get_type_sort_value() return types[self.type] end ----Return number of days for a date range ----@return number -function Date:get_date_range_days() - if not self:is_none() or not self.related_date_range then +---@return number number of days for a date range +function OrgDate:get_date_range_days() + if not self:is_none() or not self.related_date then return 0 end - return math.abs(self.related_date_range:diff(self)) + 1 + return math.abs(self.related_date:diff(self)) + 1 end ---@param date OrgDate ---@return boolean -function Date:is_in_date_range(date) +function OrgDate:is_in_date_range(date) if self.is_date_range_start then - local ranges_same_day = self.related_date_range:is_obsolete_range_end() + local ranges_same_day = self.related_date:is_obsolete_range_end() if ranges_same_day then return false end - return date:is_between(self, self.related_date_range:subtract({ day = 1 }), 'day') + return date:is_between(self, self.related_date:subtract({ day = 1 }), 'day') end return false end @@ -747,7 +825,7 @@ end ---Range of dates, excluding date ---@param date OrgDate ---@return OrgDate[] -function Date:get_range_until(date) +function OrgDate:get_range_until(date) local this = self local dates = {} while this.timestamp < date.timestamp do @@ -757,22 +835,16 @@ function Date:get_range_until(date) return dates end ----@param format string ----@return string -function Date:format(format) - return tostring(os.date(format, self.timestamp)) -end - ---@param from OrgDate ---@param span? 'day' | 'minute' ---@return number -function Date:diff(from, span) +function OrgDate:diff(from, span) span = span or 'day' local to_date = self:start_of(span) local from_date = from:start_of(span) local diff = to_date.timestamp - from_date.timestamp - if to_date.is_dst ~= from_date.is_dst then - diff = diff + (to_date.is_dst and 3600 or -3600) + if to_date.isdst ~= from_date.isdst then + diff = diff + (to_date.isdst and 3600 or -3600) end local durations = { day = 86400, @@ -781,34 +853,34 @@ function Date:diff(from, span) return math.floor(diff / durations[span]) end ----@param span string +---@param span OrgDateSpan ---@return boolean -function Date:is_past(span) - return self:is_before(now(), span) +function OrgDate:is_past(span) + return self:is_before(OrgDate.now(), span) end ----@param span string +---@param span OrgDateSpan ---@return boolean -function Date:is_today_or_past(span) - return self:is_same_or_before(now(), span) +function OrgDate:is_today_or_past(span) + return self:is_same_or_before(OrgDate.now(), span) end ----@param span string +---@param span OrgDateSpan ---@return boolean -function Date:is_future(span) - return self:is_after(now(), span) +function OrgDate:is_future(span) + return self:is_after(OrgDate.now(), span) end ----@param span string +---@param span OrgDateSpan ---@return boolean -function Date:is_today_or_future(span) - return self:is_same_or_after(now(), span) +function OrgDate:is_today_or_future(span) + return self:is_same_or_after(OrgDate.now(), span) end ---@param from OrgDate ---@return string -function Date:humanize(from) - from = from or now() +function OrgDate:humanize(from) + from = from or OrgDate.now() local diff = self:diff(from) if diff == 0 then return 'Today' @@ -820,42 +892,43 @@ function Date:humanize(from) end ---@return boolean -function Date:is_deadline() +function OrgDate:is_deadline() return self.active and self.type == 'DEADLINE' end ---@return boolean -function Date:is_none() +function OrgDate:is_none() return self.active and self.type == 'NONE' end ---@return boolean -function Date:is_logbook() +function OrgDate:is_logbook() return self.type == 'LOGBOOK' end ---@return boolean -function Date:is_scheduled() +function OrgDate:is_scheduled() return self.active and self.type == 'SCHEDULED' end ---@return boolean -function Date:is_closed() +function OrgDate:is_closed() return self.type == 'CLOSED' end -function Date:is_planning_date() +---@return boolean +function OrgDate:is_planning_date() return self:is_deadline() or self:is_scheduled() or self:is_closed() end ---@return boolean -function Date:is_weekend() +function OrgDate:is_weekend() local isoweekday = self:get_isoweekday() return isoweekday >= 6 end ----@return table|nil -function Date:get_negative_adjustment() +---@return table | nil +function OrgDate:get_negative_adjustment() if #self.adjustments == 0 then return nil end @@ -867,7 +940,8 @@ function Date:get_negative_adjustment() return nil end -function Date:with_negative_adjustment() +---@return OrgDate +function OrgDate:with_negative_adjustment() local adj = self:get_negative_adjustment() if not adj then return self @@ -886,7 +960,7 @@ end ---Get repeater value (ex. +1w, .+1w, ++1w) ---@return string | nil -function Date:get_repeater() +function OrgDate:get_repeater() if #self.adjustments == 0 then return nil end @@ -900,19 +974,20 @@ function Date:get_repeater() end ---@return OrgDate -function Date:set_todays_date() +function OrgDate:set_todays_date() local time = os_date(os.time()) return self:set({ - year = time.year, - month = time.month, - day = time.day, + year = tonumber(time.year) or 0, + month = tonumber(time.month) or 0, + day = tonumber(time.day) or 0, }) end -function Date:apply_repeater() +---@return OrgDate +function OrgDate:apply_repeater() local repeater = self:get_repeater() local date = self - local current_time = now() + local current_time = OrgDate.now() if not repeater then return self end @@ -940,7 +1015,7 @@ end ---@param date OrgDate ---@return boolean -function Date:repeats_on(date) +function OrgDate:repeats_on(date) local repeater = self:get_repeater() if not repeater then return false @@ -955,7 +1030,7 @@ function Date:repeats_on(date) end ---@param date OrgDate -function Date:apply_repeater_until(date) +function OrgDate:apply_repeater_until(date) local repeater = self:get_repeater() if not repeater then return self @@ -972,7 +1047,7 @@ function Date:apply_repeater_until(date) end ---@return OrgDate -function Date:get_adjusted_date() +function OrgDate:get_adjusted_date() if not self:is_deadline() and not self:is_scheduled() then return self end @@ -996,84 +1071,8 @@ function Date:get_adjusted_date() end ---@return string -function Date:get_week_number() +function OrgDate:get_week_number() return self:format('%V') end ----@param line string ----@param lnum number ----@param open string ----@param datetime string ----@param close string ----@param last_match? OrgDate ----@param type? string ----@return OrgDate? -local function from_match(line, lnum, open, datetime, close, last_match, type) - local search_from = last_match ~= nil and last_match.range.end_col or 0 - local from, to = line:find(vim.pesc(open .. datetime .. close), search_from) - local is_date_range_end = last_match and last_match.is_date_range_start and line:sub(from - 2, from - 1) == '--' - local opts = { - type = type, - active = open == '<', - range = Range:new({ start_line = lnum, end_line = lnum, start_col = from, end_col = to }), - is_date_range_start = line:sub(to + 1, to + 2) == '--', - } - local parsed_date = from_string(vim.trim(datetime), opts) - if is_date_range_end then - parsed_date.is_date_range_end = true - parsed_date.related_date_range = last_match - last_match.related_date_range = parsed_date - end - - return parsed_date -end - ----@param line string ----@param lnum number ----@return OrgDate[] -local function parse_all_from_line(line, lnum) - local is_comment = line:match('^%s*#[^%+]') - if is_comment then - return {} - end - local dates = {} - for open, datetime, close in line:gmatch(pattern) do - local parsed_date = from_match(line, lnum, open, datetime, close, dates[#dates]) - if parsed_date then - table.insert(dates, parsed_date) - end - end - return dates -end - ----@param value any -local function is_date_instance(value) - return getmetatable(value) == Date -end - ----@param opts { year: number, month?: number, day?: number, hour?: number, min?: number, sec?: number } ----@return OrgDate -local function from_table(opts) - return Date:from_time_table({ - year = opts.year, - month = opts.month or 1, - day = opts.day or 1, - hour = opts.hour or 0, - min = opts.min or 0, - sec = opts.sec or 0, - }) -end -return { - parse_parts = parse_parts, - from_org_date = from_org_date, - from_string = from_string, - from_table = from_table, - now = now, - today = today, - tomorrow = tomorrow, - parse_all_from_line = parse_all_from_line, - is_valid_date = is_valid_date, - is_date_instance = is_date_instance, - from_match = from_match, - pattern = pattern, -} +return OrgDate diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index 88e3e63b2..82a2bdaed 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -193,6 +193,7 @@ function OrgMappings:_adjust_date_part(direction, amount, fallback) return string.format('%d%s', count or amount, span) end local minute_adj = get_adj('M', tonumber(config.org_time_stamp_rounding_minutes) * amount) + ---@param date OrgDate local do_replacement = function(date) local col = vim.fn.col('.') or 0 local char = vim.fn.getline('.'):sub(col, col) @@ -202,7 +203,7 @@ function OrgMappings:_adjust_date_part(direction, amount, fallback) return self:_replace_date(date) end local col_from_start = col - date.range.start_col - local parts = Date.parse_parts(raw_date_value) + local parts = Date.from_string(raw_date_value):parse_parts() local adj = nil local modify_end_time = false local part = nil @@ -276,7 +277,7 @@ function OrgMappings:_adjust_date_part(direction, amount, fallback) self:_replace_date(new_date) - if date:is_logbook() and date.related_date_range then + if date:is_logbook() and date.related_date then local item = self.files:get_closest_headline_or_nil() if item then local logbook = item:get_logbook() diff --git a/tests/plenary/object/date_spec.lua b/tests/plenary/object/date_spec.lua index 5923216ef..1577f56d9 100644 --- a/tests/plenary/object/date_spec.lua +++ b/tests/plenary/object/date_spec.lua @@ -450,13 +450,14 @@ describe('Date object', function() date_only = true, day = 15, dayname = 'Sat', - is_dst = true, + isdst = true, + wday = 7, hour = 0, min = 0, month = 5, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -482,11 +483,12 @@ describe('Date object', function() dayname = 'Sat', hour = 0, min = 0, - is_dst = true, + isdst = true, month = 5, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, + wday = 7, range = Range:new({ start_line = 1, end_line = 1, @@ -504,12 +506,13 @@ describe('Date object', function() day = 15, dayname = 'Tue', hour = 9, + wday = 3, min = 25, month = 6, - is_dst = true, + isdst = true, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -535,11 +538,12 @@ describe('Date object', function() dayname = 'Sat', hour = 0, min = 0, - is_dst = true, + isdst = true, month = 5, + wday = 7, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -559,10 +563,11 @@ describe('Date object', function() hour = 0, min = 0, month = 5, - is_dst = true, + wday = 7, + isdst = true, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -691,11 +696,12 @@ describe('Date object', function() dayname = 'Sat', hour = 14, min = 30, - is_dst = true, + isdst = true, month = 5, + wday = 7, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -716,10 +722,11 @@ describe('Date object', function() hour = 0, min = 0, month = 5, - is_dst = true, + wday = 2, + isdst = true, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, + related_date = nil, range = Range:new({ start_line = 1, end_line = 1, @@ -744,12 +751,13 @@ describe('Date object', function() day = 15, dayname = 'Sat', hour = 0, - is_dst = true, + isdst = true, + wday = 7, min = 0, month = 5, is_date_range_start = true, is_date_range_end = false, - related_date_range = dates[2], + related_date = dates[2], range = Range:new({ start_line = 1, end_line = 1, @@ -768,11 +776,12 @@ describe('Date object', function() dayname = 'Sun', hour = 0, min = 0, + wday = 1, month = 5, - is_dst = true, + isdst = true, is_date_range_start = false, is_date_range_end = true, - related_date_range = dates[1], + related_date = dates[1], range = Range:new({ start_line = 1, end_line = 1, @@ -791,11 +800,12 @@ describe('Date object', function() dayname = 'Mon', hour = 0, min = 0, + wday = 2, month = 5, is_date_range_start = false, is_date_range_end = false, - related_date_range = nil, - is_dst = true, + related_date = nil, + isdst = true, range = Range:new({ start_line = 1, end_line = 1, diff --git a/tests/plenary/ui/mappings/todo_spec.lua b/tests/plenary/ui/mappings/todo_spec.lua index 2767f3605..2dbcc8190 100644 --- a/tests/plenary/ui/mappings/todo_spec.lua +++ b/tests/plenary/ui/mappings/todo_spec.lua @@ -144,7 +144,7 @@ describe('Todo mappings', function() }, vim.api.nvim_buf_get_lines(0, 2, 12, false)) vim.fn.cursor(3, 1) - local now = Date.now() + now = Date.now() vim.cmd([[norm cit]]) vim.wait(200) assert.are.same({