From 07f8b28e6ccef68d33ef07259972048c6fe2783a Mon Sep 17 00:00:00 2001 From: Aseem Ratha Date: Fri, 19 Dec 2025 14:02:54 -0500 Subject: [PATCH 1/4] Adds insert drawer function and keymap to iD + Inserts drawer with inputted name under cursor or inserts properties drawer under current headline if binding is called with prefix (e.g. count 1) + Adds tests for drawer insertion functionality --- lua/orgmode/config/defaults.lua | 1 + lua/orgmode/config/mappings/init.lua | 4 ++ lua/orgmode/org/mappings.lua | 37 ++++++++++- .../ui/mappings/insert_drawer_spec.lua | 65 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 tests/plenary/ui/mappings/insert_drawer_spec.lua diff --git a/lua/orgmode/config/defaults.lua b/lua/orgmode/config/defaults.lua index 2a5ebd64f..a6553be45 100644 --- a/lua/orgmode/config/defaults.lua +++ b/lua/orgmode/config/defaults.lua @@ -193,6 +193,7 @@ local DefaultConfig = { org_time_stamp = 'i.', org_time_stamp_inactive = 'i!', org_toggle_timestamp_type = 'd!', + org_insert_drawer = 'iD', 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..d5d7881f8 100644 --- a/lua/orgmode/config/mappings/init.lua +++ b/lua/orgmode/config/mappings/init.lua @@ -329,6 +329,10 @@ 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_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..6f30ea9b1 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -1,5 +1,6 @@ local Calendar = require('orgmode.objects.calendar') local Date = require('orgmode.objects.date') +-- local Drawer = require('orgmode.files.elements.drawer') local EditSpecial = require('orgmode.objects.edit_special') local Help = require('orgmode.objects.help') local OrgHyperlink = require('orgmode.org.links.hyperlink') @@ -794,7 +795,7 @@ end function OrgMappings:insert_link() local link = OrgHyperlink.at_cursor() return Input.open('Links: ', link and link.url:to_string() or '', function(arg_lead) - return self.completion:complete_links_from_input(arg_lead) + return self.completion:prompt(arg_lead) end):next(function(link_location) if not link_location then return false @@ -1057,6 +1058,40 @@ function OrgMappings:org_toggle_timestamp_type() self:_replace_date(date) end +-- Inserts a new drawer after the cursor position with custom name, +-- or adds PROPERTIES drawer under headline if inserted with prefix (e.g. count 1) +function OrgMappings:insert_drawer() + local headline = self.files:get_closest_headline_or_nil() + local indent = headline and headline:get_indent() or vim.fn.matchstr(vim.fn.getline('.'), '^%s*') + + if vim.v.count > 0 then + if not headline then + return utils.echo_warning('No headline found to insert a drawer under') + 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 + + 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 + ---@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..53323a5b1 --- /dev/null +++ b/tests/plenary/ui/mappings/insert_drawer_spec.lua @@ -0,0 +1,65 @@ +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.v.count = 0 + 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 when count prefix is provided', function() + helpers.create_file({ + '* TODO heading', + 'content line', + }) + + vim.fn.cursor(1, 1) + vim.v.count = 1 + orgmode.action('org_mappings.insert_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) From 9cbb614c228e059f577496cb1ed76b4f08cc9d36 Mon Sep 17 00:00:00 2001 From: Aseem Ratha Date: Fri, 19 Dec 2025 15:32:53 -0500 Subject: [PATCH 2/4] Reverts unintended changes --- lua/orgmode/org/mappings.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index 6f30ea9b1..64e81f727 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -1,6 +1,5 @@ local Calendar = require('orgmode.objects.calendar') local Date = require('orgmode.objects.date') --- local Drawer = require('orgmode.files.elements.drawer') local EditSpecial = require('orgmode.objects.edit_special') local Help = require('orgmode.objects.help') local OrgHyperlink = require('orgmode.org.links.hyperlink') @@ -795,7 +794,7 @@ end function OrgMappings:insert_link() local link = OrgHyperlink.at_cursor() return Input.open('Links: ', link and link.url:to_string() or '', function(arg_lead) - return self.completion:prompt(arg_lead) + return self.completion:complete_links_from_input(arg_lead) end):next(function(link_location) if not link_location then return false From 8ec2786e64480f70a78b9c706a92723c60656faa Mon Sep 17 00:00:00 2001 From: Aseem Ratha Date: Fri, 19 Dec 2025 15:56:37 -0500 Subject: [PATCH 3/4] fix: should prevent unintended insert_drawer_spec test failure --- tests/plenary/ui/mappings/insert_drawer_spec.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/plenary/ui/mappings/insert_drawer_spec.lua b/tests/plenary/ui/mappings/insert_drawer_spec.lua index 53323a5b1..a5e4a0b8e 100644 --- a/tests/plenary/ui/mappings/insert_drawer_spec.lua +++ b/tests/plenary/ui/mappings/insert_drawer_spec.lua @@ -14,7 +14,6 @@ end describe('Insert drawer mappings', function() after_each(function() - vim.v.count = 0 vim.cmd([[silent! %bw!]]) end) @@ -48,7 +47,7 @@ describe('Insert drawer mappings', function() }) vim.fn.cursor(1, 1) - vim.v.count = 1 + vim.cmd([[let v:count = 1]]) orgmode.action('org_mappings.insert_drawer') vim.wait(50, function() return false From b9314da825662a11df949823aa04728505a83daa Mon Sep 17 00:00:00 2001 From: Aseem Ratha Date: Fri, 19 Dec 2025 16:21:31 -0500 Subject: [PATCH 4/4] fix: removes prefix checking for propterties insertion, instead adds keybind Fixes prefix count testing issues by splitting insert_drawer and insert_properties_drawer into two different functions and bindings - insert drawer iD - insert properties drawer ip --- lua/orgmode/config/defaults.lua | 1 + lua/orgmode/config/mappings/init.lua | 4 ++ lua/orgmode/org/mappings.lua | 41 +++++++++++-------- .../ui/mappings/insert_drawer_spec.lua | 11 +++-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/lua/orgmode/config/defaults.lua b/lua/orgmode/config/defaults.lua index a6553be45..8596fbc9a 100644 --- a/lua/orgmode/config/defaults.lua +++ b/lua/orgmode/config/defaults.lua @@ -194,6 +194,7 @@ local DefaultConfig = { 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 d5d7881f8..8032ef574 100644 --- a/lua/orgmode/config/mappings/init.lua +++ b/lua/orgmode/config/mappings/init.lua @@ -333,6 +333,10 @@ return { '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 64e81f727..3f7ed6de3 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -1057,23 +1057,9 @@ function OrgMappings:org_toggle_timestamp_type() self:_replace_date(date) end --- Inserts a new drawer after the cursor position with custom name, --- or adds PROPERTIES drawer under headline if inserted with prefix (e.g. count 1) +-- Inserts a new drawer after the cursor position with custom name function OrgMappings:insert_drawer() - local headline = self.files:get_closest_headline_or_nil() - local indent = headline and headline:get_indent() or vim.fn.matchstr(vim.fn.getline('.'), '^%s*') - - if vim.v.count > 0 then - if not headline then - return utils.echo_warning('No headline found to insert a drawer under') - 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 + 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 @@ -1091,6 +1077,29 @@ function OrgMappings:insert_drawer() 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 index a5e4a0b8e..76f14086e 100644 --- a/tests/plenary/ui/mappings/insert_drawer_spec.lua +++ b/tests/plenary/ui/mappings/insert_drawer_spec.lua @@ -34,21 +34,20 @@ describe('Insert drawer mappings', function() assert.are.same({ '* TODO heading', 'content line', - ' :NOTES:', - ' ', - ' :END:', + ':NOTES:', + '', + ':END:', }, vim.api.nvim_buf_get_lines(0, 0, -1, false)) end) - it('inserts PROPERTIES drawer under headline when count prefix is provided', function() + it('inserts PROPERTIES drawer under headline', function() helpers.create_file({ '* TODO heading', 'content line', }) vim.fn.cursor(1, 1) - vim.cmd([[let v:count = 1]]) - orgmode.action('org_mappings.insert_drawer') + orgmode.action('org_mappings.insert_properties_drawer') vim.wait(50, function() return false end)