diff --git a/lua/orgmode/files/headline.lua b/lua/orgmode/files/headline.lua index d54987249..0787ea78c 100644 --- a/lua/orgmode/files/headline.lua +++ b/lua/orgmode/files/headline.lua @@ -288,6 +288,49 @@ function Headline:set_tags(tags) vim.api.nvim_buf_set_text(bufnr, pred_end_row, pred_end_col, pred_end_row, end_col, { text }) end +---@param tag string +---@return boolean newly_added +function Headline:add_tag(tag) + local current_tags = self:get_own_tags() + local present = vim.tbl_contains(current_tags, tag) + if not present then + table.insert(current_tags, tag) + end + self:set_tags(utils.tags_to_string(current_tags)) + return not present +end + +---@param tag string +---@return boolean newly_removed +function Headline:remove_tag(tag) + local current_tags = self:get_own_tags() + ---@type string[] + local new_tags = vim.tbl_filter(function(i) + return i ~= tag + end, current_tags) + local present = #new_tags ~= #current_tags + if present then + self:set_tags(utils.tags_to_string(new_tags)) + end + return present +end + +---@param tag string +---@return boolean newly_added +function Headline:toggle_tag(tag) + local current_tags = self:get_own_tags() + local present = vim.tbl_contains(current_tags, tag) + if present then + current_tags = vim.tbl_filter(function(i) + return i ~= tag + end, current_tags) + else + table.insert(current_tags, tag) + end + self:set_tags(utils.tags_to_string(current_tags)) + return not present +end + function Headline:align_tags() local own_tags, node = self:get_own_tags() if node then diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index b23ccf079..ffab6caeb 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -71,19 +71,10 @@ function OrgMappings:set_tags(tags) end) end +---@return nil function OrgMappings:toggle_archive_tag() local headline = self.files:get_closest_headline() - local current_tags = headline:get_own_tags() - - if vim.tbl_contains(current_tags, 'ARCHIVE') then - current_tags = vim.tbl_filter(function(tag) - return tag ~= 'ARCHIVE' - end, current_tags) - else - table.insert(current_tags, 'ARCHIVE') - end - - return headline:set_tags(utils.tags_to_string(current_tags)) + headline:toggle_tag('ARCHIVE') end function OrgMappings:cycle() diff --git a/tests/plenary/files/headline_spec.lua b/tests/plenary/files/headline_spec.lua index 47adf874a..19d492467 100644 --- a/tests/plenary/files/headline_spec.lua +++ b/tests/plenary/files/headline_spec.lua @@ -152,4 +152,139 @@ describe('Headline', function() assert_range(second_headline_dates[3], { 5, 3, 5, 18 }) end) end) + + describe('tags', function() + ---@type OrgFile + local file + local orig_tags_column + + before_each(function() + -- Put tags flush to headlines for shorter tests. + if not orig_tags_column then + orig_tags_column = config.org_tags_column + end + config:extend({ org_tags_column = 0 }) + -- Reinitialize test file to same state. + if not file then + file = helpers.load_file(vim.fn.tempname() .. '.org') + end + local bufnr = file:get_valid_bufnr() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { + '* Headline 1', + '* Headline 2 :other:', + '* Headline 3 :other:more:ARCHIVE:', + }) + file:reload_sync() + end) + + after_each(function() + config:extend({ org_tags_column = orig_tags_column }) + end) + + describe('toggling', function() + it('adds a tag where there is none', function() + file:get_headlines()[1]:toggle_tag('ARCHIVE') + local expected = { + '* Headline 1 :ARCHIVE:', + '* Headline 2 :other:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('adds a tag if another already exists', function() + file:get_headlines()[2]:toggle_tag('ARCHIVE') + local expected = { + '* Headline 1', + '* Headline 2 :other:ARCHIVE:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('removes an existing tag', function() + file:get_headlines()[2]:toggle_tag('other') + local expected = { + '* Headline 1', + '* Headline 2', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('keeps other tags when removing one', function() + file:get_headlines()[3]:toggle_tag('more') + local expected = { + '* Headline 1', + '* Headline 2 :other:', + '* Headline 3 :other:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + end) + + describe('addition', function() + it('adds a tag where there is none', function() + file:get_headlines()[1]:add_tag('ARCHIVE') + local expected = { + '* Headline 1 :ARCHIVE:', + '* Headline 2 :other:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('adds a tag if another already exists', function() + file:get_headlines()[2]:add_tag('ARCHIVE') + local expected = { + '* Headline 1', + '* Headline 2 :other:ARCHIVE:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('does not add the same tag twice', function() + file:get_headlines()[2]:add_tag('other') + local expected = { + '* Headline 1', + '* Headline 2 :other:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + end) + + describe('removal', function() + it('removes an existing tag', function() + file:get_headlines()[2]:remove_tag('other') + local expected = { + '* Headline 1', + '* Headline 2', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('keeps other tags when removing one', function() + file:get_headlines()[3]:remove_tag('more') + local expected = { + '* Headline 1', + '* Headline 2 :other:', + '* Headline 3 :other:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + + it('does nothing when removing a non-existent tag', function() + file:get_headlines()[1]:remove_tag('other') + local expected = { + '* Headline 1', + '* Headline 2 :other:', + '* Headline 3 :other:more:ARCHIVE:', + } + assert.are.same(expected, file:reload_sync().lines) + end) + end) + end) end)