diff --git a/lua/orgmode/config/defaults.lua b/lua/orgmode/config/defaults.lua index 2a5ebd64f..8596fbc9a 100644 --- a/lua/orgmode/config/defaults.lua +++ b/lua/orgmode/config/defaults.lua @@ -193,6 +193,8 @@ local DefaultConfig = { org_time_stamp = 'i.', org_time_stamp_inactive = 'i!', org_toggle_timestamp_type = 'd!', + org_insert_drawer = 'iD', + org_insert_properties_drawer = 'ip', org_insert_link = 'li', org_store_link = 'ls', org_clock_in = 'xi', diff --git a/lua/orgmode/config/mappings/init.lua b/lua/orgmode/config/mappings/init.lua index 96bccd02a..8032ef574 100644 --- a/lua/orgmode/config/mappings/init.lua +++ b/lua/orgmode/config/mappings/init.lua @@ -329,6 +329,14 @@ return { args = { true }, opts = { desc = 'org timestamp (inactive)', help_desc = 'Insert/Update inactive date under cursor' }, }), + org_insert_drawer = m.action( + 'org_mappings.insert_drawer', + { opts = { desc = 'org insert drawer', help_desc = 'Insert drawer' } } + ), + org_insert_properties_drawer = m.action( + 'org_mappings.insert_properties_drawer', + { opts = { desc = 'org insert properties drawer', help_desc = 'Insert Properties drawer' } } + ), org_insert_link = m.action('org_mappings.insert_link', { modes = { 'n', 'x' }, opts = { diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index 336ab3c79..3f7ed6de3 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -1057,6 +1057,49 @@ function OrgMappings:org_toggle_timestamp_type() self:_replace_date(date) end +-- Inserts a new drawer after the cursor position with custom name +function OrgMappings:insert_drawer() + local indent = vim.fn.matchstr(vim.fn.getline('.'), '^%s*') + + return Input.open('Drawer name: '):next(function(drawer_name) + if not drawer_name or vim.trim(drawer_name) == '' then + return utils.echo_warning('Drawer name cannot be empty') + end + local insert_line = vim.fn.line('.') + local drawer_lines = { + string.format('%s:%s:', indent, drawer_name), + indent, + string.format('%s:END:', indent), + } + vim.fn.append(insert_line, drawer_lines) + vim.fn.cursor(insert_line + 2, #indent + 1) + return vim.cmd([[startinsert!]]) + end) +end + +-- Inserts a PROPERTIES drawer under the current headline +function OrgMappings:insert_properties_drawer() + local headline = self.files:get_closest_headline_or_nil() + if not headline then + return utils.echo_warning('No headline found to insert a drawer under') + end + + local indent = headline:get_indent() + local existing = headline:get_drawer('PROPERTIES') + + if existing then + local start_row = existing:start() + 1 -- 1-based + vim.fn.cursor(start_row + 1, #indent + 1) + return vim.cmd([[startinsert!]]) + end + + local drawer_lines = headline:_apply_indent({ ':PROPERTIES:', '', ':END:' }) --[[ @as string[] ]] + local insert_line = headline:get_range().start_line + vim.fn.append(insert_line, drawer_lines) + vim.fn.cursor(insert_line + 2, #indent + 1) + return vim.cmd([[startinsert!]]) +end + ---@param direction string ---@param use_fast_access? boolean ---@return boolean diff --git a/tests/plenary/ui/mappings/insert_drawer_spec.lua b/tests/plenary/ui/mappings/insert_drawer_spec.lua new file mode 100644 index 000000000..76f14086e --- /dev/null +++ b/tests/plenary/ui/mappings/insert_drawer_spec.lua @@ -0,0 +1,63 @@ +local helpers = require('tests.plenary.helpers') +local Input = require('orgmode.ui.input') +local Promise = require('orgmode.utils.promise') +local orgmode = require('orgmode') + +local function with_mock_input(value, fn) + local original = Input.open + Input.open = function() + return Promise.resolve(value) + end + fn() + Input.open = original +end + +describe('Insert drawer mappings', function() + after_each(function() + vim.cmd([[silent! %bw!]]) + end) + + it('inserts custom drawer at cursor (org_insert_drawer)', function() + helpers.create_file({ + '* TODO heading', + 'content line', + }) + + vim.fn.cursor(2, 1) + with_mock_input('NOTES', function() + orgmode.action('org_mappings.insert_drawer') + vim.wait(50, function() + return false + end) + end) + + assert.are.same({ + '* TODO heading', + 'content line', + ':NOTES:', + '', + ':END:', + }, vim.api.nvim_buf_get_lines(0, 0, -1, false)) + end) + + it('inserts PROPERTIES drawer under headline', function() + helpers.create_file({ + '* TODO heading', + 'content line', + }) + + vim.fn.cursor(1, 1) + orgmode.action('org_mappings.insert_properties_drawer') + vim.wait(50, function() + return false + end) + + assert.are.same({ + '* TODO heading', + ' :PROPERTIES:', + ' ', + ' :END:', + 'content line', + }, vim.api.nvim_buf_get_lines(0, 0, -1, false)) + end) +end)