From 1eb433faa1594ed56dd959c48988948984b08a4b Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Sat, 15 Nov 2025 02:14:26 -0500 Subject: [PATCH 01/15] feat: Add english short ordinals as a new misc.alias --- lua/dial/augend/misc.lua | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lua/dial/augend/misc.lua b/lua/dial/augend/misc.lua index 253944d..5a204a1 100644 --- a/lua/dial/augend/misc.lua +++ b/lua/dial/augend/misc.lua @@ -37,4 +37,40 @@ M.alias.markdown_header = user.new { end, } +M.alias.ordinals = user.new { + ---@param line string + ---@param cursor? integer + ---@return textrange? + find = function(line, cursor) + local mark_start, mark_end = line:find "-?%d+%a%a" + local _, check_end = line:find "-?%d+%a+" + + if mark_start == nil or check_end ~= mark_end then + return nil + end + + return { from = mark_start, to = mark_end } + end, + ---@param text string + ---@param addend integer + ---@param cursor? integer + ---@return { text?: string, cursor?: integer } + add = function(text, addend, cursor) + local special_suffix = { "st", "nd", "rd" } + + for ordinal in text:gmatch "-?%d+" do + local cardinal = ordinal + addend + local remainder = math.abs(cardinal) % 100 + + local suffix = not vim.tbl_contains({ 11, 12, 13 }, remainder) and special_suffix[remainder % 10] or "th" + + text = cardinal .. suffix + end + + cursor = 1 + + return { text = text, cursor = cursor } + end, +} + return M From b43cb3bbd55b28aac6da8aae97561e538a263da9 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Sat, 15 Nov 2025 02:14:56 -0500 Subject: [PATCH 02/15] feat: Add tests for english short ordinals --- tests/dial/augend/misc_spec.lua | 113 ++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/dial/augend/misc_spec.lua b/tests/dial/augend/misc_spec.lua index c398157..16a60e0 100644 --- a/tests/dial/augend/misc_spec.lua +++ b/tests/dial/augend/misc_spec.lua @@ -27,3 +27,116 @@ describe("Test of misc.alias.markdown_header:", function() end) end) end) + +describe("Test of misc.alias.ordinals:", function() + local augend = misc.alias.ordinals + + describe("find function", function() + it("can find ordinals", function() + assert.are.same(augend:find("1st", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("2nd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("3rd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("4th", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("0th", 1), { from = 1, to = 3 }) + + assert.are.same(augend:find("10th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("11th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("12th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("13th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("21st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("22nd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("23rd", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("100th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("1000th", 1), { from = 1, to = 6 }) + + assert.are.same(augend:find("-1st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-2nd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-3rd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-4th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("-10th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-11th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-12th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-13th", 1), { from = 1, to = 5 }) + + assert.are.same(augend:find("-21st", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-22nd", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-23rd", 1), { from = 1, to = 5 }) + + assert.are.same(augend:find("-100th", 1), { from = 1, to = 6 }) + assert.are.same(augend:find("-1000th", 1), { from = 1, to = 7 }) + end) + it("ignores non-ordinal elements", function() + assert.are.same(augend:find("1standard", 1), nil) + assert.are.same(augend:find("3rdev", 1), nil) + assert.are.same(augend:find("10thousand", 1), nil) + end) + end) + + describe("add function", function() + it("can increment ordinal", function() + assert.are.same(augend:add("1st", 1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("1st", 2, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("1st", 3, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("1st", 9, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("2nd", 1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", 1, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("9th", 1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", 1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", 1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", 1, 1), { text = "13th", cursor = 1 }) + + assert.are.same(augend:add("20th", 1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", 1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22nd", 1, 1), { text = "23rd", cursor = 1 }) + + assert.are.same(augend:add("99th", 1, 1), { text = "100th", cursor = 1 }) + assert.are.same(augend:add("999th", 1, 1), { text = "1000th", cursor = 1 }) + + assert.are.same(augend:add("1000th", -1, 1), { text = "999th", cursor = 1 }) + assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) + + assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) + assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) + + assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) + assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", -1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", -1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", -1, 1), { text = "9th", cursor = 1 }) + assert.are.same(augend:add("4th", -1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", -1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("2nd", -1, 1), { text = "1st", cursor = 1 }) + + assert.are.same(augend:add("1st", -1, 1), { text = "0th", cursor = 1 }) + assert.are.same(augend:add("1st", -2, 1), { text = "-1st", cursor = 1 }) + assert.are.same(augend:add("1st", -3, 1), { text = "-2nd", cursor = 1 }) + assert.are.same(augend:add("1st", -4, 1), { text = "-3rd", cursor = 1 }) + assert.are.same(augend:add("1st", -5, 1), { text = "-4th", cursor = 1 }) + + assert.are.same(augend:add("0th", -1, 1), { text = "-1st", cursor = 1 }) + assert.are.same(augend:add("-1st", -1, 1), { text = "-2nd", cursor = 1 }) + assert.are.same(augend:add("-2nd", -1, 1), { text = "-3rd", cursor = 1 }) + assert.are.same(augend:add("-3rd", -1, 1), { text = "-4th", cursor = 1 }) + assert.are.same(augend:add("-9th", -1, 1), { text = "-10th", cursor = 1 }) + + assert.are.same(augend:add("-10th", -1, 1), { text = "-11th", cursor = 1 }) + assert.are.same(augend:add("-11th", -1, 1), { text = "-12th", cursor = 1 }) + assert.are.same(augend:add("-12th", -1, 1), { text = "-13th", cursor = 1 }) + + assert.are.same(augend:add("-20th", -1, 1), { text = "-21st", cursor = 1 }) + assert.are.same(augend:add("-21st", -1, 1), { text = "-22nd", cursor = 1 }) + assert.are.same(augend:add("-22nd", -1, 1), { text = "-23rd", cursor = 1 }) + + assert.are.same(augend:add("-99th", -1, 1), { text = "-100th", cursor = 1 }) + assert.are.same(augend:add("-999th", -1, 1), { text = "-1000th", cursor = 1 }) + end) + end) +end) From de335c89e756fb88d5208e06e89046cb6fc83226 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Sat, 15 Nov 2025 03:45:15 -0500 Subject: [PATCH 03/15] fix: Replace find function with modified common.find_pattern Was unable to have multiple ordinals per line, and was unable to have ordinals on the same line after some text similar to `5thousand` or `3rdev` before this fix was implemented --- lua/dial/augend/misc.lua | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lua/dial/augend/misc.lua b/lua/dial/augend/misc.lua index 5a204a1..33c0c0c 100644 --- a/lua/dial/augend/misc.lua +++ b/lua/dial/augend/misc.lua @@ -42,14 +42,24 @@ M.alias.ordinals = user.new { ---@param cursor? integer ---@return textrange? find = function(line, cursor) - local mark_start, mark_end = line:find "-?%d+%a%a" - local _, check_end = line:find "-?%d+%a+" + local idx_start = 1 - if mark_start == nil or check_end ~= mark_end then - return nil + while idx_start <= #line do + local mark_start, mark_end = line:find("-?%d+%a%a", idx_start) + local _, check_end = line:find("-?%d+%a+", idx_start) + + if mark_start then + if (cursor == nil or cursor <= mark_end) and check_end == mark_end then + return { from = mark_start, to = mark_end } + else + idx_start = mark_end + 1 + end + else + break + end end - return { from = mark_start, to = mark_end } + return nil end, ---@param text string ---@param addend integer From afda5707bd751b11b7581aa2d5afa29a58791538 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Sat, 15 Nov 2025 03:49:11 -0500 Subject: [PATCH 04/15] feat: Add tests for the fix implemented in de335c89 --- tests/dial/augend/misc_spec.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/dial/augend/misc_spec.lua b/tests/dial/augend/misc_spec.lua index 16a60e0..da49c4a 100644 --- a/tests/dial/augend/misc_spec.lua +++ b/tests/dial/augend/misc_spec.lua @@ -67,11 +67,17 @@ describe("Test of misc.alias.ordinals:", function() assert.are.same(augend:find("-100th", 1), { from = 1, to = 6 }) assert.are.same(augend:find("-1000th", 1), { from = 1, to = 7 }) + + assert.are.same(augend:find("1001st", 4), { from = 1, to = 6 }) + assert.are.same(augend:find("test the 2nd", 1), { from = 10, to = 12 }) + assert.are.same(augend:find("1st 2nd 3rd", 9), { from = 9, to = 11 }) end) it("ignores non-ordinal elements", function() assert.are.same(augend:find("1standard", 1), nil) assert.are.same(augend:find("3rdev", 1), nil) assert.are.same(augend:find("10thousand", 1), nil) + + assert.are.same(augend:find("5thousand 2nd", 1), { from = 11, to = 13 }) end) end) From f213d5ba02f9af6a4eecdff632000a0e630f0d9f Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Sat, 15 Nov 2025 03:56:55 -0500 Subject: [PATCH 05/15] feat: Add augend.misc aliases to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6954733..cb14e68 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,8 @@ require("dial.config").augends:register_group{ |`augend.constant.alias.alpha` |Lowercase alphabet letter (word) |`a`, `b`, `c`, ..., `z` | |`augend.constant.alias.Alpha` |Uppercase alphabet letter (word) |`A`, `B`, `C`, ..., `Z` | |`augend.semver.alias.semver` |Semantic version |`0.3.0`, `1.22.1`, `3.9.1`, ... | +|`augend.misc.alias.markdown_header` |ATX-Style markdown headings |`# This is a title`, `### Notes` | +|`augend.misc.alias.ordinals` |Shortform ordinal numbers |..., `-1st`, `0th`, `1st`, `2nd`, `3rd`, `4th`, ,,, | If you don't specify any settings, the following augends is set as the value of the `default` group. From d2badcf2fed1d1a20b813f44ba4c8ee86410f23a Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Mon, 24 Nov 2025 18:13:06 -0500 Subject: [PATCH 06/15] refactor!: Refactor ordinals from `misc.lua` to `ordinal.lua` Refactor ordinal augend from a `misc` alias to its own augend type BREAKING CHANGE: creation of new augend type --- lua/dial/augend.lua | 2 + lua/dial/augend/misc.lua | 46 -------------- lua/dial/augend/ordinal.lua | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 lua/dial/augend/ordinal.lua diff --git a/lua/dial/augend.lua b/lua/dial/augend.lua index 699270c..987f5a4 100644 --- a/lua/dial/augend.lua +++ b/lua/dial/augend.lua @@ -4,6 +4,7 @@ local date = require "dial.augend.date" local decimal_fraction = require "dial.augend.decimal_fraction" local hexcolor = require "dial.augend.hexcolor" local integer = require "dial.augend.integer" +local ordinal = require "dial.augend.ordinal" local semver = require "dial.augend.semver" local user = require "dial.augend.user" local paren = require "dial.augend.paren" @@ -16,6 +17,7 @@ return { decimal_fraction = decimal_fraction, hexcolor = hexcolor, integer = integer, + ordinal = ordinal, semver = semver, user = user, paren = paren, diff --git a/lua/dial/augend/misc.lua b/lua/dial/augend/misc.lua index 33c0c0c..253944d 100644 --- a/lua/dial/augend/misc.lua +++ b/lua/dial/augend/misc.lua @@ -37,50 +37,4 @@ M.alias.markdown_header = user.new { end, } -M.alias.ordinals = user.new { - ---@param line string - ---@param cursor? integer - ---@return textrange? - find = function(line, cursor) - local idx_start = 1 - - while idx_start <= #line do - local mark_start, mark_end = line:find("-?%d+%a%a", idx_start) - local _, check_end = line:find("-?%d+%a+", idx_start) - - if mark_start then - if (cursor == nil or cursor <= mark_end) and check_end == mark_end then - return { from = mark_start, to = mark_end } - else - idx_start = mark_end + 1 - end - else - break - end - end - - return nil - end, - ---@param text string - ---@param addend integer - ---@param cursor? integer - ---@return { text?: string, cursor?: integer } - add = function(text, addend, cursor) - local special_suffix = { "st", "nd", "rd" } - - for ordinal in text:gmatch "-?%d+" do - local cardinal = ordinal + addend - local remainder = math.abs(cardinal) % 100 - - local suffix = not vim.tbl_contains({ 11, 12, 13 }, remainder) and special_suffix[remainder % 10] or "th" - - text = cardinal .. suffix - end - - cursor = 1 - - return { text = text, cursor = cursor } - end, -} - return M diff --git a/lua/dial/augend/ordinal.lua b/lua/dial/augend/ordinal.lua new file mode 100644 index 0000000..01f5e5a --- /dev/null +++ b/lua/dial/augend/ordinal.lua @@ -0,0 +1,116 @@ +local util = require "dial.util" + +---@alias AugendOrdinalConfig {} + +---@class AugendOrdinal +---@implement Augend +---@field natural boolean +---@field query string +---@field suffix { default: string, special?: string[] } +---@field case '"lower"' | '"upper"' | '"prefer_lower"' | '"prefer_upper"' +local AugendOrdinal = {} + +local M = {} + +---@param config { natural?: boolean, suffix?: table, case?: '"lower"' | '"upper"' | '"prefer_lower"' | '"prefer_upper"' } +---@return Augend +function M.new(config) + vim.validate("natural", config.natural, "boolean", true) + vim.validate("suffix", config.suffix, "table", true) + vim.validate("case", config.case, "string", true) + + local natural = util.unwrap_or(config.natural, true) + local case = util.unwrap_or(config.case, "lower") + + local suffix = util.unwrap_or(config.suffix, { + default = "th", + special = { + "st", + "nd", + "rd", + }, + }) + + local query = "%d+" .. string.rep("%a", #suffix.default) + + if not natural then + query = "-?" .. query + end + + return setmetatable({ + natural = natural, + query = query, + suffix = suffix, + case = case, + }, { __index = AugendOrdinal }) +end + +---@param line string +---@param cursor? integer +---@return textrange? +function AugendOrdinal:find(line, cursor) + local idx_start = 1 + + local check_query = "%d+%a+" + + if not self.natural then + check_query = "-?" .. check_query + end + + while idx_start <= #line do + local mark_start, mark_end = line:find(self.query, idx_start) + local _, check_end = line:find(check_query, idx_start) + + if mark_start then + if (cursor == nil or cursor <= mark_end) and check_end == mark_end then + return { from = mark_start, to = mark_end } + else + idx_start = mark_end + 1 + end + else + break + end + end + + return nil +end + +---@param text string +---@param addend integer +---@param cursor? integer +---@return { text?: string, cursor?: integer } +function AugendOrdinal:add(text, addend, cursor) + local ordinal_query = "%d+" + + if not self.natural then + ordinal_query = "-?" .. ordinal_query + end + + for ordinal in text:gmatch(ordinal_query) do + local cardinal = ordinal + addend + + if (cardinal < 0) and self.natural then + cardinal = 0 + end + + local remainder = math.abs(cardinal) % 100 + + -- WARN: the following statement only works for the english language + local suffix = not vim.tbl_contains({ 11, 12, 13 }, remainder) and self.suffix.special[remainder % 10] + or self.suffix.default + + -- TODO: make use of `case` to changing final casing of suffix + text = cardinal .. suffix + end + + cursor = 1 + + return { text = text, cursor = cursor } +end + +M.alias = { + en = M.new {}, + en_neg = M.new { natural = false }, +} + +return M From db2fb3cf398f89aa298074e668dcc40faa1b6e7a Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Mon, 24 Nov 2025 18:17:15 -0500 Subject: [PATCH 07/15] test: Add tests for new ordinal augend type --- tests/dial/augend/misc_spec.lua | 119 --------------- tests/dial/augend/ordinal_spec.lua | 224 +++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 119 deletions(-) create mode 100644 tests/dial/augend/ordinal_spec.lua diff --git a/tests/dial/augend/misc_spec.lua b/tests/dial/augend/misc_spec.lua index da49c4a..c398157 100644 --- a/tests/dial/augend/misc_spec.lua +++ b/tests/dial/augend/misc_spec.lua @@ -27,122 +27,3 @@ describe("Test of misc.alias.markdown_header:", function() end) end) end) - -describe("Test of misc.alias.ordinals:", function() - local augend = misc.alias.ordinals - - describe("find function", function() - it("can find ordinals", function() - assert.are.same(augend:find("1st", 1), { from = 1, to = 3 }) - assert.are.same(augend:find("2nd", 1), { from = 1, to = 3 }) - assert.are.same(augend:find("3rd", 1), { from = 1, to = 3 }) - assert.are.same(augend:find("4th", 1), { from = 1, to = 3 }) - assert.are.same(augend:find("0th", 1), { from = 1, to = 3 }) - - assert.are.same(augend:find("10th", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("11th", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("12th", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("13th", 1), { from = 1, to = 4 }) - - assert.are.same(augend:find("21st", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("22nd", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("23rd", 1), { from = 1, to = 4 }) - - assert.are.same(augend:find("100th", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("1000th", 1), { from = 1, to = 6 }) - - assert.are.same(augend:find("-1st", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("-2nd", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("-3rd", 1), { from = 1, to = 4 }) - assert.are.same(augend:find("-4th", 1), { from = 1, to = 4 }) - - assert.are.same(augend:find("-10th", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("-11th", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("-12th", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("-13th", 1), { from = 1, to = 5 }) - - assert.are.same(augend:find("-21st", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("-22nd", 1), { from = 1, to = 5 }) - assert.are.same(augend:find("-23rd", 1), { from = 1, to = 5 }) - - assert.are.same(augend:find("-100th", 1), { from = 1, to = 6 }) - assert.are.same(augend:find("-1000th", 1), { from = 1, to = 7 }) - - assert.are.same(augend:find("1001st", 4), { from = 1, to = 6 }) - assert.are.same(augend:find("test the 2nd", 1), { from = 10, to = 12 }) - assert.are.same(augend:find("1st 2nd 3rd", 9), { from = 9, to = 11 }) - end) - it("ignores non-ordinal elements", function() - assert.are.same(augend:find("1standard", 1), nil) - assert.are.same(augend:find("3rdev", 1), nil) - assert.are.same(augend:find("10thousand", 1), nil) - - assert.are.same(augend:find("5thousand 2nd", 1), { from = 11, to = 13 }) - end) - end) - - describe("add function", function() - it("can increment ordinal", function() - assert.are.same(augend:add("1st", 1, 1), { text = "2nd", cursor = 1 }) - assert.are.same(augend:add("1st", 2, 1), { text = "3rd", cursor = 1 }) - assert.are.same(augend:add("1st", 3, 1), { text = "4th", cursor = 1 }) - assert.are.same(augend:add("1st", 9, 1), { text = "10th", cursor = 1 }) - - assert.are.same(augend:add("2nd", 1, 1), { text = "3rd", cursor = 1 }) - assert.are.same(augend:add("3rd", 1, 1), { text = "4th", cursor = 1 }) - assert.are.same(augend:add("9th", 1, 1), { text = "10th", cursor = 1 }) - - assert.are.same(augend:add("10th", 1, 1), { text = "11th", cursor = 1 }) - assert.are.same(augend:add("11th", 1, 1), { text = "12th", cursor = 1 }) - assert.are.same(augend:add("12th", 1, 1), { text = "13th", cursor = 1 }) - - assert.are.same(augend:add("20th", 1, 1), { text = "21st", cursor = 1 }) - assert.are.same(augend:add("21st", 1, 1), { text = "22nd", cursor = 1 }) - assert.are.same(augend:add("22nd", 1, 1), { text = "23rd", cursor = 1 }) - - assert.are.same(augend:add("99th", 1, 1), { text = "100th", cursor = 1 }) - assert.are.same(augend:add("999th", 1, 1), { text = "1000th", cursor = 1 }) - - assert.are.same(augend:add("1000th", -1, 1), { text = "999th", cursor = 1 }) - assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) - - assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) - assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) - assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) - assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) - - assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) - assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) - assert.are.same(augend:add("12th", -1, 1), { text = "11th", cursor = 1 }) - assert.are.same(augend:add("11th", -1, 1), { text = "10th", cursor = 1 }) - - assert.are.same(augend:add("10th", -1, 1), { text = "9th", cursor = 1 }) - assert.are.same(augend:add("4th", -1, 1), { text = "3rd", cursor = 1 }) - assert.are.same(augend:add("3rd", -1, 1), { text = "2nd", cursor = 1 }) - assert.are.same(augend:add("2nd", -1, 1), { text = "1st", cursor = 1 }) - - assert.are.same(augend:add("1st", -1, 1), { text = "0th", cursor = 1 }) - assert.are.same(augend:add("1st", -2, 1), { text = "-1st", cursor = 1 }) - assert.are.same(augend:add("1st", -3, 1), { text = "-2nd", cursor = 1 }) - assert.are.same(augend:add("1st", -4, 1), { text = "-3rd", cursor = 1 }) - assert.are.same(augend:add("1st", -5, 1), { text = "-4th", cursor = 1 }) - - assert.are.same(augend:add("0th", -1, 1), { text = "-1st", cursor = 1 }) - assert.are.same(augend:add("-1st", -1, 1), { text = "-2nd", cursor = 1 }) - assert.are.same(augend:add("-2nd", -1, 1), { text = "-3rd", cursor = 1 }) - assert.are.same(augend:add("-3rd", -1, 1), { text = "-4th", cursor = 1 }) - assert.are.same(augend:add("-9th", -1, 1), { text = "-10th", cursor = 1 }) - - assert.are.same(augend:add("-10th", -1, 1), { text = "-11th", cursor = 1 }) - assert.are.same(augend:add("-11th", -1, 1), { text = "-12th", cursor = 1 }) - assert.are.same(augend:add("-12th", -1, 1), { text = "-13th", cursor = 1 }) - - assert.are.same(augend:add("-20th", -1, 1), { text = "-21st", cursor = 1 }) - assert.are.same(augend:add("-21st", -1, 1), { text = "-22nd", cursor = 1 }) - assert.are.same(augend:add("-22nd", -1, 1), { text = "-23rd", cursor = 1 }) - - assert.are.same(augend:add("-99th", -1, 1), { text = "-100th", cursor = 1 }) - assert.are.same(augend:add("-999th", -1, 1), { text = "-1000th", cursor = 1 }) - end) - end) -end) diff --git a/tests/dial/augend/ordinal_spec.lua b/tests/dial/augend/ordinal_spec.lua new file mode 100644 index 0000000..18984be --- /dev/null +++ b/tests/dial/augend/ordinal_spec.lua @@ -0,0 +1,224 @@ +local ordinal = require("dial.augend").ordinal + +describe("Test of ordinal.alias.en:", function() + local augend = ordinal.alias.en + + describe("find function", function() + it("can find ordinals", function() + assert.are.same(augend:find("1st", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("2nd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("3rd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("4th", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("0th", 1), { from = 1, to = 3 }) + + assert.are.same(augend:find("10th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("11th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("12th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("13th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("21st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("22nd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("23rd", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("100th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("1000th", 1), { from = 1, to = 6 }) + + assert.are.same(augend:find("-1st", 1), { from = 2, to = 4 }) + assert.are.same(augend:find("-2nd", 1), { from = 2, to = 4 }) + assert.are.same(augend:find("-3rd", 1), { from = 2, to = 4 }) + assert.are.same(augend:find("-4th", 1), { from = 2, to = 4 }) + + assert.are.same(augend:find("-10th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-11th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-12th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-13th", 1), { from = 2, to = 5 }) + + assert.are.same(augend:find("-21st", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-22nd", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-23rd", 1), { from = 2, to = 5 }) + + assert.are.same(augend:find("-100th", 1), { from = 2, to = 6 }) + assert.are.same(augend:find("-1000th", 1), { from = 2, to = 7 }) + + assert.are.same(augend:find("1001st", 4), { from = 1, to = 6 }) + assert.are.same(augend:find("test the 2nd", 1), { from = 10, to = 12 }) + assert.are.same(augend:find("1st 2nd 3rd", 9), { from = 9, to = 11 }) + + assert.are.same(augend:find("manifest-2nd.txt", 1), { from = 10, to = 12 }) + assert.are.same(augend:find("---1st LDoc comment", 1), { from = 4, to = 6 }) + end) + it("ignores non-ordinal elements", function() + assert.are.same(augend:find("1standard", 1), nil) + assert.are.same(augend:find("3rdev", 1), nil) + assert.are.same(augend:find("10thousand", 1), nil) + + assert.are.same(augend:find("5thousand 2nd", 1), { from = 11, to = 13 }) + end) + end) + + describe("add function", function() + it("can increment ordinal", function() + assert.are.same(augend:add("1st", 1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("1st", 2, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("1st", 3, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("1st", 9, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("2nd", 1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", 1, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("9th", 1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", 1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", 1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", 1, 1), { text = "13th", cursor = 1 }) + + assert.are.same(augend:add("20th", 1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", 1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22nd", 1, 1), { text = "23rd", cursor = 1 }) + + assert.are.same(augend:add("99th", 1, 1), { text = "100th", cursor = 1 }) + assert.are.same(augend:add("999th", 1, 1), { text = "1000th", cursor = 1 }) + + assert.are.same(augend:add("1000th", -1, 1), { text = "999th", cursor = 1 }) + assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) + + assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) + assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) + + assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) + assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", -1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", -1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", -1, 1), { text = "9th", cursor = 1 }) + assert.are.same(augend:add("4th", -1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", -1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("2nd", -1, 1), { text = "1st", cursor = 1 }) + + assert.are.same(augend:add("1st", -1, 1), { text = "0th", cursor = 1 }) + assert.are.same(augend:add("1st", -2, 1), { text = "0th", cursor = 1 }) + + assert.are.same(augend:add("0th", -1, 1), { text = "0th", cursor = 1 }) + end) + end) +end) + +describe("Test of ordinal.alias.en_neg:", function() + local augend = ordinal.alias.en_neg + + describe("find function", function() + it("can find ordinals", function() + assert.are.same(augend:find("1st", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("2nd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("3rd", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("4th", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("0th", 1), { from = 1, to = 3 }) + + assert.are.same(augend:find("10th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("11th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("12th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("13th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("21st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("22nd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("23rd", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("100th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("1000th", 1), { from = 1, to = 6 }) + + assert.are.same(augend:find("-1st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-2nd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-3rd", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("-4th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("-10th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-11th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-12th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-13th", 1), { from = 1, to = 5 }) + + assert.are.same(augend:find("-21st", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-22nd", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("-23rd", 1), { from = 1, to = 5 }) + + assert.are.same(augend:find("-100th", 1), { from = 1, to = 6 }) + assert.are.same(augend:find("-1000th", 1), { from = 1, to = 7 }) + + assert.are.same(augend:find("1001st", 4), { from = 1, to = 6 }) + assert.are.same(augend:find("test the 2nd", 1), { from = 10, to = 12 }) + assert.are.same(augend:find("1st 2nd 3rd", 9), { from = 9, to = 11 }) + end) + it("ignores non-ordinal elements", function() + assert.are.same(augend:find("1standard", 1), nil) + assert.are.same(augend:find("3rdev", 1), nil) + assert.are.same(augend:find("10thousand", 1), nil) + + assert.are.same(augend:find("5thousand 2nd", 1), { from = 11, to = 13 }) + end) + end) + + describe("add function", function() + it("can increment ordinal", function() + assert.are.same(augend:add("1st", 1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("1st", 2, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("1st", 3, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("1st", 9, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("2nd", 1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", 1, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("9th", 1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", 1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", 1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", 1, 1), { text = "13th", cursor = 1 }) + + assert.are.same(augend:add("20th", 1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", 1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22nd", 1, 1), { text = "23rd", cursor = 1 }) + + assert.are.same(augend:add("99th", 1, 1), { text = "100th", cursor = 1 }) + assert.are.same(augend:add("999th", 1, 1), { text = "1000th", cursor = 1 }) + + assert.are.same(augend:add("1000th", -1, 1), { text = "999th", cursor = 1 }) + assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) + + assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) + assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) + + assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) + assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", -1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", -1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", -1, 1), { text = "9th", cursor = 1 }) + assert.are.same(augend:add("4th", -1, 1), { text = "3rd", cursor = 1 }) + assert.are.same(augend:add("3rd", -1, 1), { text = "2nd", cursor = 1 }) + assert.are.same(augend:add("2nd", -1, 1), { text = "1st", cursor = 1 }) + + assert.are.same(augend:add("1st", -1, 1), { text = "0th", cursor = 1 }) + assert.are.same(augend:add("1st", -2, 1), { text = "-1st", cursor = 1 }) + assert.are.same(augend:add("1st", -3, 1), { text = "-2nd", cursor = 1 }) + assert.are.same(augend:add("1st", -4, 1), { text = "-3rd", cursor = 1 }) + assert.are.same(augend:add("1st", -5, 1), { text = "-4th", cursor = 1 }) + + assert.are.same(augend:add("0th", -1, 1), { text = "-1st", cursor = 1 }) + assert.are.same(augend:add("-1st", -1, 1), { text = "-2nd", cursor = 1 }) + assert.are.same(augend:add("-2nd", -1, 1), { text = "-3rd", cursor = 1 }) + assert.are.same(augend:add("-3rd", -1, 1), { text = "-4th", cursor = 1 }) + assert.are.same(augend:add("-9th", -1, 1), { text = "-10th", cursor = 1 }) + + assert.are.same(augend:add("-10th", -1, 1), { text = "-11th", cursor = 1 }) + assert.are.same(augend:add("-11th", -1, 1), { text = "-12th", cursor = 1 }) + assert.are.same(augend:add("-12th", -1, 1), { text = "-13th", cursor = 1 }) + + assert.are.same(augend:add("-20th", -1, 1), { text = "-21st", cursor = 1 }) + assert.are.same(augend:add("-21st", -1, 1), { text = "-22nd", cursor = 1 }) + assert.are.same(augend:add("-22nd", -1, 1), { text = "-23rd", cursor = 1 }) + + assert.are.same(augend:add("-99th", -1, 1), { text = "-100th", cursor = 1 }) + assert.are.same(augend:add("-999th", -1, 1), { text = "-1000th", cursor = 1 }) + end) + end) +end) From 29a2a5740898a0ab47aa6bc1bb8c94e74b23c1a5 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Mon, 24 Nov 2025 18:24:16 -0500 Subject: [PATCH 08/15] docs: Update alias table in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb14e68..d11d0d6 100644 --- a/README.md +++ b/README.md @@ -367,8 +367,9 @@ require("dial.config").augends:register_group{ |`augend.constant.alias.alpha` |Lowercase alphabet letter (word) |`a`, `b`, `c`, ..., `z` | |`augend.constant.alias.Alpha` |Uppercase alphabet letter (word) |`A`, `B`, `C`, ..., `Z` | |`augend.semver.alias.semver` |Semantic version |`0.3.0`, `1.22.1`, `3.9.1`, ... | +|`augend.ordinal.alias.en` |English shortform ordinal numbers |`0th`, `1st`, `2nd`, `3rd`, `4th`, ... | +|`augend.ordinal.alias.en_neg` |English shortform ordinal numbers including negatives |..., `-1st`, `0th`, `1st`, `2nd`, `3rd`, `4th`, ... | |`augend.misc.alias.markdown_header` |ATX-Style markdown headings |`# This is a title`, `### Notes` | -|`augend.misc.alias.ordinals` |Shortform ordinal numbers |..., `-1st`, `0th`, `1st`, `2nd`, `3rd`, `4th`, ,,, | If you don't specify any settings, the following augends is set as the value of the `default` group. From e58c8bcf3bdef73237b02cfb437a9696a82d48bc Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Mon, 24 Nov 2025 18:24:58 -0500 Subject: [PATCH 09/15] chore: Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02dc863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.todo.md From 0deba0020fa20b9dfccfbdf822722e06dbf585b8 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 00:36:25 -0500 Subject: [PATCH 10/15] feat: Add query checking to `common.lua` --- lua/dial/augend/common.lua | 53 ++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/lua/dial/augend/common.lua b/lua/dial/augend/common.lua index 37d5fd8..0173c70 100644 --- a/lua/dial/augend/common.lua +++ b/lua/dial/augend/common.lua @@ -7,8 +7,9 @@ local M = {} ---augend の find field を簡単に実装する。 ---@param ptn string ---@param allow_match_before_cursor? boolean +---@param check_query? string ---@return findf -function M.find_pattern(ptn, allow_match_before_cursor) +function M.find_pattern(ptn, allow_match_before_cursor, check_query) ---@param line string ---@param cursor? integer ---@return textrange? @@ -17,7 +18,22 @@ function M.find_pattern(ptn, allow_match_before_cursor) local idx_start = 1 while idx_start <= #line do local s, e = line:find(ptn, idx_start) - if s then + if not s then + -- 検索結果がなければそこで終了 + break + end + local check_e + if check_query then + _, check_e = line:find(check_query, idx_start) + end + if check_e then + if (cursor == nil or cursor <= e) and check_e == e then + return { from = s, to = e } + else + match_before_cursor = { from = s, to = e } + idx_start = e + 1 + end + else -- 検索結果があったら if cursor == nil or cursor <= e then -- cursor が終了文字より後ろにあったら終了 @@ -27,9 +43,6 @@ function M.find_pattern(ptn, allow_match_before_cursor) -- 終了文字の後ろから探し始める idx_start = e + 1 end - else - -- 検索結果がなければそこで終了 - break end end if allow_match_before_cursor then @@ -43,8 +56,9 @@ end -- augend の find field を簡単に実装する。 ---@param ptn string ---@param allow_match_before_cursor? boolean +---@param check_query? string ---@return findf -function M.find_pattern_regex(ptn, allow_match_before_cursor) +function M.find_pattern_regex(ptn, allow_match_before_cursor, check_query) ---@param line string ---@param cursor? integer ---@return textrange? @@ -53,11 +67,25 @@ function M.find_pattern_regex(ptn, allow_match_before_cursor) local idx_start = 1 while idx_start <= #line do local s, e = vim.regex(ptn):match_str(line:sub(idx_start)) - - if s then - s = s + idx_start -- 上で得られた s は相対位置なので - e = e + idx_start - 1 -- 上で得られた s は相対位置なので - + if not s then + -- 検索結果がなければそこで終了 + break + end + local check_e + if check_query then + _, check_e = vim.regex(check_query):match_str(line:sub(idx_start)) + end + s = s + idx_start -- 上で得られた s は相対位置なので + e = e + idx_start - 1 -- 上で得られた s は相対位置なので + if check_e then + check_e = check_e + idx_start - 1 + if (cursor == nil or cursor <= e) and check_e == e then + return { from = s, to = e } + else + match_before_cursor = { from = s, to = e } + idx_start = e + 1 + end + else -- 検索結果があったら if cursor == nil or cursor <= e then -- cursor が終了文字より後ろにあったら終了 @@ -67,9 +95,6 @@ function M.find_pattern_regex(ptn, allow_match_before_cursor) -- 終了文字の後ろから探し始める idx_start = e + 1 end - else - -- 検索結果がなければそこで終了 - break end end if allow_match_before_cursor then From 25e7f64ee3946ec74004a60608111423c6639592 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 00:37:32 -0500 Subject: [PATCH 11/15] feat: Update augend to use new query checking features --- lua/dial/augend/ordinal.lua | 106 ++++++++++++++---------------------- 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/lua/dial/augend/ordinal.lua b/lua/dial/augend/ordinal.lua index 01f5e5a..a00d962 100644 --- a/lua/dial/augend/ordinal.lua +++ b/lua/dial/augend/ordinal.lua @@ -1,84 +1,28 @@ +local common = require "dial.augend.common" local util = require "dial.util" ----@alias AugendOrdinalConfig {} +---@alias ordinalSuffix { default: string, special?: string[] } ----@class AugendOrdinal ----@implement Augend +---@alias AugendOrdinalConfig { natural?: boolean, suffix?: ordinalSuffix } + +---@class AugendOrdinal: Augend ---@field natural boolean +---@field suffix ordinalSuffix ---@field query string ----@field suffix { default: string, special?: string[] } ----@field case '"lower"' | '"upper"' | '"prefer_lower"' | '"prefer_upper"' +---@field check_query? string local AugendOrdinal = {} -local M = {} - ----@param config { natural?: boolean, suffix?: table, case?: '"lower"' | '"upper"' | '"prefer_lower"' | '"prefer_upper"' } ----@return Augend -function M.new(config) - vim.validate("natural", config.natural, "boolean", true) - vim.validate("suffix", config.suffix, "table", true) - vim.validate("case", config.case, "string", true) - - local natural = util.unwrap_or(config.natural, true) - local case = util.unwrap_or(config.case, "lower") - - local suffix = util.unwrap_or(config.suffix, { - default = "th", - special = { - "st", - "nd", - "rd", - }, - }) - - local query = "%d+" .. string.rep("%a", #suffix.default) - - if not natural then - query = "-?" .. query - end - - return setmetatable({ - natural = natural, - query = query, - suffix = suffix, - case = case, - }, { __index = AugendOrdinal }) -end - ---@param line string ---@param cursor? integer ---@return textrange? function AugendOrdinal:find(line, cursor) - local idx_start = 1 - - local check_query = "%d+%a+" - - if not self.natural then - check_query = "-?" .. check_query - end - - while idx_start <= #line do - local mark_start, mark_end = line:find(self.query, idx_start) - local _, check_end = line:find(check_query, idx_start) - - if mark_start then - if (cursor == nil or cursor <= mark_end) and check_end == mark_end then - return { from = mark_start, to = mark_end } - else - idx_start = mark_end + 1 - end - else - break - end - end - - return nil + return common.find_pattern_regex(self.query, false, self.check_query)(line, cursor) end ---@param text string ---@param addend integer ---@param cursor? integer ----@return { text?: string, cursor?: integer } +---@return addresult function AugendOrdinal:add(text, addend, cursor) local ordinal_query = "%d+" @@ -99,7 +43,6 @@ function AugendOrdinal:add(text, addend, cursor) local suffix = not vim.tbl_contains({ 11, 12, 13 }, remainder) and self.suffix.special[remainder % 10] or self.suffix.default - -- TODO: make use of `case` to changing final casing of suffix text = cardinal .. suffix end @@ -108,6 +51,37 @@ function AugendOrdinal:add(text, addend, cursor) return { text = text, cursor = cursor } end +local M = {} + +---@param config AugendOrdinalConfig +---@return Augend +function M.new(config) + vim.validate("natural", config.natural, "boolean", true) + vim.validate("suffix", config.suffix, "table", true) + + local natural = util.unwrap_or(config.natural, true) + + local suffix = util.unwrap_or(config.suffix, { + default = "th", + special = { + "st", + "nd", + "rd", + }, + }) + + -- WARN: the following queries only work for the english language + local query = ([[\V%s\d\+\a\{1,2}]]):format(util.if_expr(natural, "", [[-\?]])) + local check_query = ([[\V%s\d\+\a\+]]):format(util.if_expr(natural, "", [[-\?]])) + + return setmetatable({ + natural = natural, + suffix = suffix, + query = query, + check_query = check_query, + }, { __index = AugendOrdinal }) +end + M.alias = { en = M.new {}, en_neg = M.new { natural = false }, From 28b73f56189f88a6a9279e16fd6ad360dbb66029 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 00:38:47 -0500 Subject: [PATCH 12/15] fix: Fixed several broken tests --- tests/dial/augend/ordinal_spec.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/dial/augend/ordinal_spec.lua b/tests/dial/augend/ordinal_spec.lua index 18984be..5b61c93 100644 --- a/tests/dial/augend/ordinal_spec.lua +++ b/tests/dial/augend/ordinal_spec.lua @@ -82,9 +82,9 @@ describe("Test of ordinal.alias.en:", function() assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) - assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) - assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) - assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) + assert.are.same(augend:add("23rd", -1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22nd", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", -1, 1), { text = "20th", cursor = 1 }) assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) @@ -183,9 +183,9 @@ describe("Test of ordinal.alias.en_neg:", function() assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) assert.are.same(augend:add("24th", -1, 1), { text = "23rd", cursor = 1 }) - assert.are.same(augend:add("23th", -1, 1), { text = "22nd", cursor = 1 }) - assert.are.same(augend:add("22th", -1, 1), { text = "21st", cursor = 1 }) - assert.are.same(augend:add("21th", -1, 1), { text = "20th", cursor = 1 }) + assert.are.same(augend:add("23rd", -1, 1), { text = "22nd", cursor = 1 }) + assert.are.same(augend:add("22nd", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", -1, 1), { text = "20th", cursor = 1 }) assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) From 1c409a55da3000c53ce28b7488908f715fcc65a1 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 00:40:20 -0500 Subject: [PATCH 13/15] feat: Add archaic english variant listed on Wikipedia --- lua/dial/augend/ordinal.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/dial/augend/ordinal.lua b/lua/dial/augend/ordinal.lua index a00d962..7459de7 100644 --- a/lua/dial/augend/ordinal.lua +++ b/lua/dial/augend/ordinal.lua @@ -85,6 +85,16 @@ end M.alias = { en = M.new {}, en_neg = M.new { natural = false }, + en_old = M.new { + suffix = { + default = "th", + special = { + "st", + "d", + "d", + }, + }, + }, } return M From ba0ec4ad3eecee0d7ca9fd1fdea0b5ef19786275 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 00:41:13 -0500 Subject: [PATCH 14/15] test: Add tests for archaic english variant --- tests/dial/augend/ordinal_spec.lua | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/dial/augend/ordinal_spec.lua b/tests/dial/augend/ordinal_spec.lua index 5b61c93..6407b77 100644 --- a/tests/dial/augend/ordinal_spec.lua +++ b/tests/dial/augend/ordinal_spec.lua @@ -222,3 +222,107 @@ describe("Test of ordinal.alias.en_neg:", function() end) end) end) + +describe("Test of ordinal.alias.en_old:", function() + local augend = ordinal.alias.en_old + + describe("find function", function() + it("can find ordinals", function() + assert.are.same(augend:find("1st", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("2d", 1), { from = 1, to = 2 }) + assert.are.same(augend:find("3d", 1), { from = 1, to = 2 }) + assert.are.same(augend:find("4th", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("0th", 1), { from = 1, to = 3 }) + + assert.are.same(augend:find("10th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("11th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("12th", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("13th", 1), { from = 1, to = 4 }) + + assert.are.same(augend:find("21st", 1), { from = 1, to = 4 }) + assert.are.same(augend:find("22d", 1), { from = 1, to = 3 }) + assert.are.same(augend:find("23d", 1), { from = 1, to = 3 }) + + assert.are.same(augend:find("100th", 1), { from = 1, to = 5 }) + assert.are.same(augend:find("1000th", 1), { from = 1, to = 6 }) + + assert.are.same(augend:find("-1st", 1), { from = 2, to = 4 }) + assert.are.same(augend:find("-2d", 1), { from = 2, to = 3 }) + assert.are.same(augend:find("-3d", 1), { from = 2, to = 3 }) + assert.are.same(augend:find("-4th", 1), { from = 2, to = 4 }) + + assert.are.same(augend:find("-10th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-11th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-12th", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-13th", 1), { from = 2, to = 5 }) + + assert.are.same(augend:find("-21st", 1), { from = 2, to = 5 }) + assert.are.same(augend:find("-22d", 1), { from = 2, to = 4 }) + assert.are.same(augend:find("-23d", 1), { from = 2, to = 4 }) + + assert.are.same(augend:find("-100th", 1), { from = 2, to = 6 }) + assert.are.same(augend:find("-1000th", 1), { from = 2, to = 7 }) + + assert.are.same(augend:find("1001st", 4), { from = 1, to = 6 }) + assert.are.same(augend:find("test the 2d", 1), { from = 10, to = 11 }) + assert.are.same(augend:find("1st 2d 3d", 9), { from = 8, to = 9 }) + + assert.are.same(augend:find("manifest-2d.txt", 1), { from = 10, to = 11 }) + assert.are.same(augend:find("---1st LDoc comment", 1), { from = 4, to = 6 }) + end) + it("ignores non-ordinal elements", function() + assert.are.same(augend:find("1standard", 1), nil) + assert.are.same(augend:find("3dev", 1), nil) + assert.are.same(augend:find("10thousand", 1), nil) + + assert.are.same(augend:find("5thousand 2d", 1), { from = 11, to = 12 }) + end) + end) + + describe("add function", function() + it("can increment ordinal", function() + assert.are.same(augend:add("1st", 1, 1), { text = "2d", cursor = 1 }) + assert.are.same(augend:add("1st", 2, 1), { text = "3d", cursor = 1 }) + assert.are.same(augend:add("1st", 3, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("1st", 9, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("2d", 1, 1), { text = "3d", cursor = 1 }) + assert.are.same(augend:add("3d", 1, 1), { text = "4th", cursor = 1 }) + assert.are.same(augend:add("9th", 1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", 1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", 1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", 1, 1), { text = "13th", cursor = 1 }) + + assert.are.same(augend:add("20th", 1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", 1, 1), { text = "22d", cursor = 1 }) + assert.are.same(augend:add("22d", 1, 1), { text = "23d", cursor = 1 }) + + assert.are.same(augend:add("99th", 1, 1), { text = "100th", cursor = 1 }) + assert.are.same(augend:add("999th", 1, 1), { text = "1000th", cursor = 1 }) + + assert.are.same(augend:add("1000th", -1, 1), { text = "999th", cursor = 1 }) + assert.are.same(augend:add("100th", -1, 1), { text = "99th", cursor = 1 }) + + assert.are.same(augend:add("24th", -1, 1), { text = "23d", cursor = 1 }) + assert.are.same(augend:add("23d", -1, 1), { text = "22d", cursor = 1 }) + assert.are.same(augend:add("22d", -1, 1), { text = "21st", cursor = 1 }) + assert.are.same(augend:add("21st", -1, 1), { text = "20th", cursor = 1 }) + + assert.are.same(augend:add("14th", -1, 1), { text = "13th", cursor = 1 }) + assert.are.same(augend:add("13th", -1, 1), { text = "12th", cursor = 1 }) + assert.are.same(augend:add("12th", -1, 1), { text = "11th", cursor = 1 }) + assert.are.same(augend:add("11th", -1, 1), { text = "10th", cursor = 1 }) + + assert.are.same(augend:add("10th", -1, 1), { text = "9th", cursor = 1 }) + assert.are.same(augend:add("4th", -1, 1), { text = "3d", cursor = 1 }) + assert.are.same(augend:add("3d", -1, 1), { text = "2d", cursor = 1 }) + assert.are.same(augend:add("2d", -1, 1), { text = "1st", cursor = 1 }) + + assert.are.same(augend:add("1st", -1, 1), { text = "0th", cursor = 1 }) + assert.are.same(augend:add("1st", -2, 1), { text = "0th", cursor = 1 }) + + assert.are.same(augend:add("0th", -1, 1), { text = "0th", cursor = 1 }) + end) + end) +end) From f82d60e65e8672c89cf192b60116c40b7c4ab4d5 Mon Sep 17 00:00:00 2001 From: NeonCarbide Date: Tue, 25 Nov 2025 01:11:09 -0500 Subject: [PATCH 15/15] docs: Add basic documentation for ordinal augend --- README.md | 1 + doc/dial.txt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 6a4325f..7134678 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,7 @@ require("dial.config").augends:register_group{ |`augend.semver.alias.semver` |Semantic version |`0.3.0`, `1.22.1`, `3.9.1`, ... | |`augend.ordinal.alias.en` |English shortform ordinal numbers |`0th`, `1st`, `2nd`, `3rd`, `4th`, ... | |`augend.ordinal.alias.en_neg` |English shortform ordinal numbers including negatives |..., `-1st`, `0th`, `1st`, `2nd`, `3rd`, `4th`, ... | +|`augend.ordinal.alias.en_old` |English shortform ordinal numbers - archaic |`0th`, `1st`, `2d`, `3d`, `4th`, ... | |`augend.misc.alias.markdown_header` |ATX-Style markdown headings |`# This is a title`, `### Notes` | diff --git a/doc/dial.txt b/doc/dial.txt index e984a2c..5088ad5 100644 --- a/doc/dial.txt +++ b/doc/dial.txt @@ -289,6 +289,7 @@ for a particular filetype. }, markdown = { augend.integer.alias.decimal, + augend.ordinal.alias.en, augend.misc.alias.markdown_header, } } @@ -337,6 +338,11 @@ The following aliases are provided by default (See |dial-augends| for detail): `augend.paren.alias.lua_str_literal` `augend.paren.alias.rust_str_literal` + ordinal: + `augend.ordinal.alias.en` + `augend.ordinal.alias.en_neg` + `augend.ordinal.alias.en_old` + misc: `augend.misc.alias.markdown_header`