diff --git a/lua/orgmode/api/init.lua b/lua/orgmode/api/init.lua index 90e6c89ef..ba5b84540 100644 --- a/lua/orgmode/api/init.lua +++ b/lua/orgmode/api/init.lua @@ -84,9 +84,12 @@ function OrgApi.refile(opts) local source_bufnr = utils.get_buffer_by_filename(opts.source.file.filename) local is_capture = source_bufnr > -1 and vim.b[source_bufnr].org_capture - - if is_capture and orgmode.capture._window then - refile_opts.template = orgmode.capture._window.template + if is_capture then + local capture_window = orgmode.capture._windows[vim.b[source_bufnr].org_capture_window_id] + if capture_window then + refile_opts.template = capture_window.template + refile_opts.capture_window = capture_window + end end return Promise.resolve() diff --git a/lua/orgmode/capture/_meta.lua b/lua/orgmode/capture/_meta.lua index 056429364..135030dd8 100644 --- a/lua/orgmode/capture/_meta.lua +++ b/lua/orgmode/capture/_meta.lua @@ -8,6 +8,7 @@ ---@class OrgProcessCaptureOpts ---@field template OrgCaptureTemplate +---@field capture_window OrgCaptureWindow ---@field source_file OrgFile ---@field source_headline? OrgHeadline ---@field destination_file OrgFile diff --git a/lua/orgmode/capture/init.lua b/lua/orgmode/capture/init.lua index 85eb8dbac..1a15b0fed 100644 --- a/lua/orgmode/capture/init.lua +++ b/lua/orgmode/capture/init.lua @@ -21,7 +21,7 @@ local Promise = require('orgmode.utils.promise') ---@field on_pre_refile OrgOnCaptureClose ---@field on_post_refile OrgOnCaptureClose ---@field on_cancel_refile OrgOnCaptureCancel ----@field _window OrgCaptureWindow +---@field private _windows table local Capture = {} Capture.__index = Capture @@ -34,6 +34,7 @@ function Capture:new(opts) this.on_cancel_refile = opts.on_cancel_refile this.templates = opts.templates or Templates:new() this.closing_note = this:_setup_closing_note() + this._windows = {} return this end @@ -72,17 +73,18 @@ end ---@param template OrgCaptureTemplate ---@return OrgPromise function Capture:open_template(template) - self._window = CaptureWindow:new({ + local window = CaptureWindow:new({ template = template, - on_open = function() + on_open = function(capture_window) + self._windows[capture_window.id] = capture_window return self:setup_mappings() end, - on_close = function() - return self:on_refile_close() + on_close = function(capture_window) + return self:on_refile_close(capture_window) end, }) - return self._window:open() + return window:open() end ---@param shortcut string @@ -94,13 +96,13 @@ function Capture:open_template_by_shortcut(shortcut) return self:open_template(template) end -function Capture:on_refile_close() - local is_modified = vim.bo.modified - local opts = self:_get_refile_vars() +---@param capture_window OrgCaptureWindow +function Capture:on_refile_close(capture_window) + local opts = self:_get_refile_vars(capture_window) if not opts then return end - if is_modified then + if capture_window:is_modified() then local choice = vim.fn.confirm(string.format('Do you want to refile this to %s?', opts.destination_file.filename), '&Yes\n&No') vim.cmd([[redraw!]]) @@ -112,15 +114,15 @@ function Capture:on_refile_close() end end - vim.defer_fn(function() + vim.schedule(function() self:_refile_from_capture_buffer(opts) - self._window = nil - end, 0) + end) end ---Triggered when refiling from capture buffer function Capture:refile() - local opts = self:_get_refile_vars() + local capture_window = self._windows[vim.b.org_capture_window_id] + local opts = self:_get_refile_vars(capture_window) if not opts then return end @@ -132,12 +134,14 @@ end function Capture:refile_to_destination() local source_file = self.files:get_current_file() local source_headline = source_file:get_headlines()[1] + local capture_window = self._windows[vim.b.org_capture_window_id] return self:get_destination():next(function(destination) if not destination then return false end return self:_refile_from_capture_buffer({ - template = self._window.template, + template = capture_window.template, + capture_window = capture_window, source_file = source_file, source_headline = source_headline, destination_file = destination.file, @@ -203,7 +207,7 @@ function Capture:_refile_from_capture_buffer(opts) self.on_post_refile(self, opts) end utils.echo_info(('Wrote %s'):format(destination_file.filename)) - self:kill() + self:kill(false, opts.capture_window.id) return true end @@ -535,22 +539,25 @@ function Capture:build_note_capture(title) end ---@param from_mapping? boolean -function Capture:kill(from_mapping) - if self._window then +---@param window_id? number +function Capture:kill(from_mapping, window_id) + local window = self._windows[window_id or vim.b.org_capture_window_id] + if window then if from_mapping and self.on_cancel_refile then self.on_cancel_refile(self) end - self._window:kill() - self._window = nil + window:kill() + self._windows[window.id] = nil end end ---@private +---@param capture_window OrgCaptureWindow ---@return OrgProcessCaptureOpts | false -function Capture:_get_refile_vars() - local source_file = self.files:get_current_file() +function Capture:_get_refile_vars(capture_window) + local source_file = self.files:get(vim.api.nvim_buf_get_name(capture_window:get_bufnr())) local source_headline = nil - if not self._window.template.whole_file then + if not capture_window.template.whole_file then source_headline = source_file:get_headlines()[1] end @@ -559,7 +566,8 @@ function Capture:_get_refile_vars() source_headline = source_headline, destination_file = nil, destination_headline = nil, - template = self._window.template, + template = capture_window.template, + capture_window = capture_window, } if self.on_pre_refile then diff --git a/lua/orgmode/capture/window.lua b/lua/orgmode/capture/window.lua index 669150e61..43567c69f 100644 --- a/lua/orgmode/capture/window.lua +++ b/lua/orgmode/capture/window.lua @@ -1,6 +1,7 @@ local config = require('orgmode.config') local utils = require('orgmode.utils') local Promise = require('orgmode.utils.promise') +local id_counter = 0 ---@class OrgCaptureWindowOpts ---@field template OrgCaptureTemplate @@ -9,6 +10,7 @@ local Promise = require('orgmode.utils.promise') ---@field on_close? fun(self: OrgCaptureWindow) ---@class OrgCaptureWindow :OrgCaptureWindowOpts +---@field id number ---@field private _window fun() | nil ---@field private _bufnr number local CaptureWindow = {} @@ -22,6 +24,8 @@ function CaptureWindow:new(opts) on_finish = opts.on_finish, on_close = opts.on_close, } + data.id = id_counter + id_counter = id_counter + 1 return setmetatable(data, CaptureWindow) end @@ -38,6 +42,7 @@ function CaptureWindow:open() vim.api.nvim_buf_set_lines(0, 0, -1, true, content) self.template:setup() vim.b.org_capture = true + vim.b.org_capture_window_id = self.id self._bufnr = vim.api.nvim_get_current_buf() if self.on_open then @@ -80,6 +85,16 @@ function CaptureWindow:focus() return self end +---@return boolean +function CaptureWindow:is_modified() + return vim.bo[self._bufnr].modified +end + +---@return number +function CaptureWindow:get_bufnr() + return self._bufnr +end + function CaptureWindow:kill() if self._window then self:_window() diff --git a/lua/orgmode/ui/menu.lua b/lua/orgmode/ui/menu.lua index 9a4902fd0..d54e1846e 100644 --- a/lua/orgmode/ui/menu.lua +++ b/lua/orgmode/ui/menu.lua @@ -86,7 +86,7 @@ function Menu:add_option(option) table.insert(self.items, option) end ----@param separator OrgMenuSeparator +---@param separator? OrgMenuSeparator function Menu:add_separator(separator) self:_validate_separator(separator) table.insert(self.items, vim.tbl_deep_extend('force', self.separator, separator or {})) diff --git a/lua/orgmode/utils/init.lua b/lua/orgmode/utils/init.lua index 40f1d6edc..7f6aab203 100644 --- a/lua/orgmode/utils/init.lua +++ b/lua/orgmode/utils/init.lua @@ -2,7 +2,6 @@ local Promise = require('orgmode.utils.promise') local uv = vim.uv local utils = {} local debounce_timers = {} -local tmp_window_augroup = vim.api.nvim_create_augroup('OrgTmpWindow', { clear = true }) ---@param file string full path to filename ---@param opts? { raw: boolean, schedule: boolean } raw: Return raw results, schedule: wrap results in vim.schedule @@ -415,17 +414,17 @@ function utils.open_tmp_org_window(height, split_mode, border, on_close) utils.open_window(vim.fn.tempname() .. '.org', height or 16, split_mode, border) vim.cmd([[setlocal filetype=org bufhidden=wipe nobuflisted nolist noswapfile nofoldenable]]) local bufnr = vim.api.nvim_get_current_buf() + local augroup = vim.api.nvim_create_augroup('OrgTmpWindow_' .. bufnr, { clear = true }) if on_close then vim.api.nvim_create_autocmd('BufWipeout', { - buffer = 0, - group = tmp_window_augroup, + buffer = bufnr, + group = augroup, callback = on_close, once = true, }) vim.api.nvim_create_autocmd('VimLeavePre', { - buffer = 0, - group = tmp_window_augroup, + group = augroup, callback = on_close, once = true, }) @@ -442,7 +441,7 @@ function utils.open_tmp_org_window(height, split_mode, border, on_close) end return function() - vim.api.nvim_create_augroup('OrgTmpWindow', { clear = true }) + vim.api.nvim_create_augroup('OrgTmpWindow_' .. bufnr, { clear = true }) close_win() if prev_winnr and vim.api.nvim_win_is_valid(prev_winnr) then vim.api.nvim_set_current_win(prev_winnr) diff --git a/tests/plenary/capture/capture_spec.lua b/tests/plenary/capture/capture_spec.lua index 2e7e53990..13191b83c 100644 --- a/tests/plenary/capture/capture_spec.lua +++ b/tests/plenary/capture/capture_spec.lua @@ -1,6 +1,7 @@ local Capture = require('orgmode.capture') local Templates = require('orgmode.capture.templates') local Template = require('orgmode.capture.template') +local CaptureWindow = require('orgmode.capture.window') local helpers = require('tests.plenary.helpers') local org = require('orgmode') @@ -227,20 +228,23 @@ describe('Capture', function() local capture_lines = { '* foo' } local capture_file = helpers.create_file_instance(capture_lines) local item = capture_file:get_headlines()[1] + local template = Template:new({ + properties = { + empty_lines = { + before = 2, + after = 1, + }, + }, + }) + local capture_window = CaptureWindow:new({ template = template }) ---@diagnostic disable-next-line: invisible org.capture:_refile_from_capture_buffer({ destination_file = destination_file, source_file = capture_file, source_headline = item, - template = Template:new({ - properties = { - empty_lines = { - before = 2, - after = 1, - }, - }, - }), + template = template, + capture_window = capture_window, }) vim.cmd('edit ' .. vim.fn.fnameescape(destination_file.filename)) assert.are.same({ @@ -262,6 +266,16 @@ describe('Capture', function() local capture_lines = { '** baz' } local capture_file = helpers.create_file(capture_lines) + + local template = Template:new({ + properties = { + empty_lines = { + before = 2, + after = 1, + }, + }, + }) + local capture_window = CaptureWindow:new({ template = template }) assert(capture_file) local item = capture_file:get_headlines()[1] @@ -270,14 +284,8 @@ describe('Capture', function() destination_file = destination_file, source_file = capture_file, source_headline = item, - template = Template:new({ - properties = { - empty_lines = { - before = 2, - after = 1, - }, - }, - }), + template = template, + capture_window = capture_window, }) vim.cmd('edit ' .. vim.fn.fnameescape(destination_file.filename)) assert.are.same({ @@ -304,6 +312,15 @@ describe('Capture', function() local capture_lines = { '** baz' } local capture_file = helpers.create_file_instance(capture_lines) local item = capture_file:get_headlines()[1] + local template = Template:new({ + properties = { + empty_lines = { + before = 2, + after = 1, + }, + }, + }) + local capture_window = CaptureWindow:new({ template = template }) ---@diagnostic disable-next-line: invisible org.capture:_refile_from_capture_buffer({ @@ -311,14 +328,8 @@ describe('Capture', function() source_file = capture_file, source_headline = item, destination_headline = destination_file:get_headlines()[1], - template = Template:new({ - properties = { - empty_lines = { - before = 2, - after = 1, - }, - }, - }), + template = template, + capture_window = capture_window, }) vim.cmd('edit ' .. vim.fn.fnameescape(destination_file.filename)) assert.are.same({ @@ -346,15 +357,18 @@ describe('Capture', function() local capture_lines = { '** baz' } local capture_file = helpers.create_file_instance(capture_lines) local item = capture_file:get_headlines()[1] + local template = Template:new({ + regexp = 'appendhere', + }) + local capture_window = CaptureWindow:new({ template = template }) ---@diagnostic disable-next-line: invisible org.capture:_refile_from_capture_buffer({ destination_file = destination_file, source_file = capture_file, source_headline = item, - template = Template:new({ - regexp = 'appendhere', - }), + template = template, + capture_window = capture_window, }) vim.cmd('edit ' .. vim.fn.fnameescape(destination_file.filename)) assert.are.same({ diff --git a/tests/plenary/capture/datetree_spec.lua b/tests/plenary/capture/datetree_spec.lua index 655b96193..4a818b102 100644 --- a/tests/plenary/capture/datetree_spec.lua +++ b/tests/plenary/capture/datetree_spec.lua @@ -1,6 +1,7 @@ ---@diagnostic disable: invisible local helpers = require('tests.plenary.helpers') local Template = require('orgmode.capture.template') +local CaptureWindow = require('orgmode.capture.window') local Date = require('orgmode.objects.date') describe('Datetree', function() @@ -12,16 +13,18 @@ describe('Datetree', function() local get_template = function(date, content) local filename = vim.fn.tempname() .. '.org' vim.fn.writefile(content or {}, filename) + local template = Template:new({ + target = filename, + template = '* %?', + datetree = { + time_prompt = true, + date = date, + }, + }) return { destination_file = org.files:get(filename), - template = Template:new({ - target = filename, - template = '* %?', - datetree = { - time_prompt = true, - date = date, - }, - }), + capture_window = CaptureWindow:new({ template = template }), + template = template, } end describe('datetree does not exist', function() @@ -382,17 +385,19 @@ describe('Datetree', function() local get_template = function(date, content) local filename = vim.fn.tempname() .. '.org' vim.fn.writefile(content or {}, filename) + local template = Template:new({ + target = filename, + template = '* %?', + datetree = { + time_prompt = true, + date = date, + reversed = true, + }, + }) return { destination_file = org.files:get(filename), - template = Template:new({ - target = filename, - template = '* %?', - datetree = { - time_prompt = true, - date = date, - reversed = true, - }, - }), + capture_window = CaptureWindow:new({ template = template }), + template = template, } end describe('datetree does not exist', function() @@ -751,34 +756,36 @@ describe('Datetree', function() local get_template = function(date, content) local filename = vim.fn.tempname() .. '.org' vim.fn.writefile(content or {}, filename) - return { - destination_file = org.files:get(filename), - template = Template:new({ - target = filename, - template = '* %?', - datetree = { - time_prompt = true, - date = date, - tree_type = 'custom', - tree = { - { - format = '%Y', - pattern = '^(%d%d%d%d)$', - order = { 1 }, - }, - { - format = '%m/%Y %B', - pattern = '^(%d%d)%/(%d%d%d%d).*$', - order = { 2, 1 }, - }, - { - format = '%m/%d/%Y %A', - pattern = '^(%d%d)/(%d%d)/(%d%d%d%d).*$', - order = { 3, 1, 2 }, - }, + local template = Template:new({ + target = filename, + template = '* %?', + datetree = { + time_prompt = true, + date = date, + tree_type = 'custom', + tree = { + { + format = '%Y', + pattern = '^(%d%d%d%d)$', + order = { 1 }, + }, + { + format = '%m/%Y %B', + pattern = '^(%d%d)%/(%d%d%d%d).*$', + order = { 2, 1 }, + }, + { + format = '%m/%d/%Y %A', + pattern = '^(%d%d)/(%d%d)/(%d%d%d%d).*$', + order = { 3, 1, 2 }, }, }, - }), + }, + }) + return { + destination_file = org.files:get(filename), + capture_window = CaptureWindow:new({ template = template }), + template = template, } end describe('datetree does not exist', function()