From bf06121cd69480ea5b25a9ae2fd44a783b3feb36 Mon Sep 17 00:00:00 2001 From: Celso Benedetti Date: Tue, 3 Feb 2026 21:26:24 -0300 Subject: [PATCH 1/4] fix: better tag parsing for hyphens and whitespaces 1. hyphens are replaced with underscores 2. whitespaces are treated as separators between multiple tags --- lua/orgmode/files/headline.lua | 7 ++++++- tests/plenary/api/api_spec.lua | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lua/orgmode/files/headline.lua b/lua/orgmode/files/headline.lua index f494b1483..c0c968c8b 100644 --- a/lua/orgmode/files/headline.lua +++ b/lua/orgmode/files/headline.lua @@ -268,7 +268,12 @@ function Headline:set_tags(tags) local end_col = line:len() local text = '' - tags = vim.trim(tags):gsub('^:', ''):gsub(':$', '') + tags = vim.trim(tags) + :gsub('-', '_') -- Replace hyphens with underscores + :gsub('[%s:]+', ':') -- Convert all whitespace and existing colons into a single colon + :gsub('^:', '') + :gsub(':$', '') + if tags ~= '' then tags = ':' .. tags .. ':' diff --git a/tests/plenary/api/api_spec.lua b/tests/plenary/api/api_spec.lua index 16636d31e..71592b43e 100644 --- a/tests/plenary/api/api_spec.lua +++ b/tests/plenary/api/api_spec.lua @@ -103,11 +103,14 @@ describe('Api', function() assert.are.same('Second level', current_file.headlines[2].title) assert.are.same({ 'WORK', 'OFFICE', 'NESTEDTAG' }, current_file.headlines[2].all_tags) - current_file.headlines[2]:set_tags({ 'PERSONAL', 'HEALTH' }):wait() - - assert.are.same({ 'PERSONAL', 'HEALTH' }, cur_file().headlines[2].tags) - assert.are.same({ 'WORK', 'OFFICE', 'PERSONAL', 'HEALTH' }, cur_file().headlines[2].all_tags) - assert.is.True(vim.fn.getline(5):match(':PERSONAL:HEALTH:$') ~= nil) + current_file.headlines[2]:set_tags({ 'PERSONAL', 'HEALTH', 'TAG1 TAG2', 'MY-HYPHEN-TAG' }):wait() + + assert.are.same({ 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'MY_HYPHEN_TAG' }, cur_file().headlines[2].tags) + assert.are.same( + { 'WORK', 'OFFICE', 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'MY_HYPHEN_TAG' }, + cur_file().headlines[2].all_tags + ) + assert.is.True(vim.fn.getline(5):match(':PERSONAL:HEALTH:TAG1:TAG2:MY_HYPHEN_TAG:$') ~= nil) end) it('should cycle upwards through priorities, starting with default', function() From d0c7530443fe43ecee077b1dfe89d941b462316c Mon Sep 17 00:00:00 2001 From: Celso Benedetti Date: Tue, 3 Feb 2026 21:51:06 -0300 Subject: [PATCH 2/4] lint: format file with stylua --- lua/orgmode/files/headline.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/orgmode/files/headline.lua b/lua/orgmode/files/headline.lua index c0c968c8b..3309ab3d1 100644 --- a/lua/orgmode/files/headline.lua +++ b/lua/orgmode/files/headline.lua @@ -268,8 +268,9 @@ function Headline:set_tags(tags) local end_col = line:len() local text = '' - tags = vim.trim(tags) - :gsub('-', '_') -- Replace hyphens with underscores + tags = vim + .trim(tags) + :gsub('-', '_') -- Replace hyphens with underscores :gsub('[%s:]+', ':') -- Convert all whitespace and existing colons into a single colon :gsub('^:', '') :gsub(':$', '') From 2578c2269fa26fbd840cc296eeb2a2be13403375 Mon Sep 17 00:00:00 2001 From: Celso Benedetti Date: Tue, 3 Feb 2026 22:06:56 -0300 Subject: [PATCH 3/4] test: edge cases for tag input --- tests/plenary/api/api_spec.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/plenary/api/api_spec.lua b/tests/plenary/api/api_spec.lua index 71592b43e..556ded3f3 100644 --- a/tests/plenary/api/api_spec.lua +++ b/tests/plenary/api/api_spec.lua @@ -113,6 +113,19 @@ describe('Api', function() assert.is.True(vim.fn.getline(5):match(':PERSONAL:HEALTH:TAG1:TAG2:MY_HYPHEN_TAG:$') ~= nil) end) + it('should handle edge cases in tag input', function() + helpers.create_file({ '* TODO Test orgmode tags :TAG1:' }) + + assert.is.True(#api.load() > 1) + local current_file = cur_file() + assert.are.same({ 'TAG1' }, current_file.headlines[1].all_tags) + + current_file.headlines[1]:set_tags({ ': -tag1- : tag2:::tag3 tag_4 : @tag5 :' }):wait() + + assert.are.same({ '_tag1_', 'tag2', 'tag3', 'tag_4', '@tag5' }, cur_file().headlines[1].all_tags) + assert.is.True(vim.fn.getline(1):match(':_tag1_:tag2:tag3:tag_4:@tag5:') ~= nil) + end) + it('should cycle upwards through priorities, starting with default', function() helpers.create_file({ '#TITLE: First file', From 98d6fdb3b296cfc5a40cb7f2c0cadae7a04c9a62 Mon Sep 17 00:00:00 2001 From: Celso Benedetti Date: Thu, 5 Feb 2026 23:51:30 -0300 Subject: [PATCH 4/4] fix: hyphens should be replaced with colons --- lua/orgmode/files/headline.lua | 3 +-- tests/plenary/api/api_spec.lua | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lua/orgmode/files/headline.lua b/lua/orgmode/files/headline.lua index 3309ab3d1..332064a55 100644 --- a/lua/orgmode/files/headline.lua +++ b/lua/orgmode/files/headline.lua @@ -270,8 +270,7 @@ function Headline:set_tags(tags) local text = '' tags = vim .trim(tags) - :gsub('-', '_') -- Replace hyphens with underscores - :gsub('[%s:]+', ':') -- Convert all whitespace and existing colons into a single colon + :gsub('[%s:-]+', ':') -- Convert all whitespace, existing colons and hyphens into a single colon :gsub('^:', '') :gsub(':$', '') diff --git a/tests/plenary/api/api_spec.lua b/tests/plenary/api/api_spec.lua index 556ded3f3..8c0748086 100644 --- a/tests/plenary/api/api_spec.lua +++ b/tests/plenary/api/api_spec.lua @@ -103,14 +103,14 @@ describe('Api', function() assert.are.same('Second level', current_file.headlines[2].title) assert.are.same({ 'WORK', 'OFFICE', 'NESTEDTAG' }, current_file.headlines[2].all_tags) - current_file.headlines[2]:set_tags({ 'PERSONAL', 'HEALTH', 'TAG1 TAG2', 'MY-HYPHEN-TAG' }):wait() + current_file.headlines[2]:set_tags({ 'PERSONAL', 'HEALTH', 'TAG1 TAG2', 'TAG3-TAG4-TAG5' }):wait() - assert.are.same({ 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'MY_HYPHEN_TAG' }, cur_file().headlines[2].tags) + assert.are.same({ 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'TAG3', 'TAG4', 'TAG5' }, cur_file().headlines[2].tags) assert.are.same( - { 'WORK', 'OFFICE', 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'MY_HYPHEN_TAG' }, + { 'WORK', 'OFFICE', 'PERSONAL', 'HEALTH', 'TAG1', 'TAG2', 'TAG3', 'TAG4', 'TAG5' }, cur_file().headlines[2].all_tags ) - assert.is.True(vim.fn.getline(5):match(':PERSONAL:HEALTH:TAG1:TAG2:MY_HYPHEN_TAG:$') ~= nil) + assert.is.True(vim.fn.getline(5):match(':PERSONAL:HEALTH:TAG1:TAG2:TAG3:TAG4:TAG5:$') ~= nil) end) it('should handle edge cases in tag input', function() @@ -122,8 +122,8 @@ describe('Api', function() current_file.headlines[1]:set_tags({ ': -tag1- : tag2:::tag3 tag_4 : @tag5 :' }):wait() - assert.are.same({ '_tag1_', 'tag2', 'tag3', 'tag_4', '@tag5' }, cur_file().headlines[1].all_tags) - assert.is.True(vim.fn.getline(1):match(':_tag1_:tag2:tag3:tag_4:@tag5:') ~= nil) + assert.are.same({ 'tag1', 'tag2', 'tag3', 'tag_4', '@tag5' }, cur_file().headlines[1].all_tags) + assert.is.True(vim.fn.getline(1):match(':tag1:tag2:tag3:tag_4:@tag5:') ~= nil) end) it('should cycle upwards through priorities, starting with default', function()