From 938f9af3877088861dd26779c555f0d9b47ffed7 Mon Sep 17 00:00:00 2001
From: Daniil Shvalov <57654917+danilshvalov@users.noreply.github.com>
Date: Thu, 26 Oct 2023 12:45:56 +0300
Subject: [PATCH] refactor: captures (#613)
* refactor: captures
* refactor: make headline detached from template
* feat: subtemplates
* refactor: move `empty_lines` to `properties`
* feat: remove buffer empty lines
* fix: properly place capture without headline
* tests: add tests for org captures
* fix: template validation
* chore: remove old tests
---
DOCS.md | 68 ++++--
lua/orgmode/capture/init.lua | 216 +++++++++++------
lua/orgmode/capture/template.lua | 85 +++++++
lua/orgmode/capture/templates.lua | 22 +-
lua/orgmode/org/mappings.lua | 10 +-
tests/plenary/capture/capture_spec.lua | 267 ++++++++++++++++++++--
tests/plenary/ui/mappings/refile_spec.lua | 21 +-
7 files changed, 581 insertions(+), 108 deletions(-)
create mode 100644 lua/orgmode/capture/template.lua
diff --git a/DOCS.md b/DOCS.md
index 22e5c19a3..5d477bbe0 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -412,6 +412,16 @@ Variables:
* `%^{PROMPT|DEFAULT|COMPLETION...}`: Prompt for input, if completion is provided an :h inputlist will be used
* `%(EXP)`: Runs the given lua code and inserts the result. NOTE: this will internally pass the content to the lua `load()` function. So the body inside `%()` should be the body of a function that returns a string.
+Templates have the following fields:
+ * `description` (`string`) — description of the template that is displayed in the template selection menu
+ * `template` (`string|string[]`) — body of the template that will be used when creating capture
+ * `target` (`string?`) — name of the file to which the capture content will be added. If the target is not specified, the content will be added to the [`org_default_notes_file`](#orgdefaultnotesfile) file
+ * `headline` (`string?`) — title of the headline after which the capture content will be added. If no headline is specified, the content will be appended to the end of the file
+ * `properties` (`table?`):
+ * `empty_lines` (`table|number?`) — if the value is a number, then empty lines are added before and after the content. If the value is a table, then the following fields are expected:
+ * `before` (`integer?`) — add empty lines to the beginning of the content
+ * `after` (`integer?`) — add empty lines to the end of the content
+
Example:
```lua
{ T = {
@@ -423,20 +433,24 @@ Example:
Journal example:
```lua
- { j = {
- description = 'Journal',
- template = '\n*** %<%Y-%m-%d> %<%A>\n**** %U\n\n%?',
- target = '~/sync/org/journal.org'
- } }
+ {
+ j = {
+ description = 'Journal',
+ template = '\n*** %<%Y-%m-%d> %<%A>\n**** %U\n\n%?',
+ target = '~/sync/org/journal.org'
+ },
+ }
```
Journal example with dynamic target, i.e. a separate file per month:
```lua
- { J = {
- description = 'Journal',
- template = '\n*** %<%Y-%m-%d> %<%A>\n**** %U\n\n%?',
- target = '~/sync/org/journal/%<%Y-%m>.org'
- } }
+ {
+ J = {
+ description = 'Journal',
+ template = '\n*** %<%Y-%m-%d> %<%A>\n**** %U\n\n%?',
+ target = '~/sync/org/journal/%<%Y-%m>.org'
+ },
+ }
```
Nested key example:
@@ -456,16 +470,38 @@ Nested key example:
headline = 'one-time'
}
}
+ -- or
+ {
+ e = {
+ description = 'Event',
+ subtemplates = {
+ r = {
+ description = 'recurring',
+ template = '** %?\n %T',
+ target = '~/org/calendar.org',
+ headline = 'recurring'
+ },
+ o = {
+ description = 'one-time',
+ template = '** %?\n %T',
+ target = '~/org/calendar.org',
+ headline = 'one-time'
+ },
+ },
+ },
+ }
```
Lua expression example:
```lua
- { j = {
- description = 'Journal',
- template = '* %(return vim.fn.getreg "w")',
- -- get the content of register "w"
- target = '~/sync/org/journal.org'
- } }
+ {
+ j = {
+ description = 'Journal',
+ template = '* %(return vim.fn.getreg "w")',
+ -- get the content of register "w"
+ target = '~/sync/org/journal.org'
+ },
+ }
```
#### **org_agenda_min_height**
diff --git a/lua/orgmode/capture/init.lua b/lua/orgmode/capture/init.lua
index dfaf0bbc8..02b74dc90 100644
--- a/lua/orgmode/capture/init.lua
+++ b/lua/orgmode/capture/init.lua
@@ -3,8 +3,18 @@ local config = require('orgmode.config')
local Files = require('orgmode.parser.files')
local File = require('orgmode.parser.file')
local Templates = require('orgmode.capture.templates')
+local Template = require('orgmode.capture.template')
local ClosingNote = require('orgmode.capture.closing_note')
local Menu = require('orgmode.ui.menu')
+local Range = require('orgmode.parser.range')
+
+---@class CaptureOpts
+---@field file string
+---@field range Range
+---@field lines string[]
+---@field template Template?
+---@field headline string?
+---@field item Section?
---@class Capture
---@field templates Templates
@@ -20,6 +30,8 @@ function Capture:new()
return data
end
+---@param base_key string
+---@param templates table
function Capture:_get_subtemplates(base_key, templates)
local subtemplates = {}
for key, template in pairs(templates) do
@@ -30,6 +42,7 @@ function Capture:_get_subtemplates(base_key, templates)
return subtemplates
end
+---@param templates table
function Capture:_create_menu_items(templates)
local menu_items = {}
for key, template in pairs(templates) do
@@ -42,6 +55,11 @@ function Capture:_create_menu_items(templates)
item.action = function()
self:_create_prompt(self:_get_subtemplates(key, templates))
end
+ elseif vim.tbl_count(template.subtemplates) > 0 then
+ item.label = template.description .. '...'
+ item.action = function()
+ self:_create_prompt(template.subtemplates)
+ end
else
item.label = template.description
item.action = function()
@@ -54,6 +72,7 @@ function Capture:_create_menu_items(templates)
return menu_items
end
+---@param templates table
function Capture:_create_prompt(templates)
local menu = Menu:new({
title = 'Select a capture template',
@@ -79,8 +98,9 @@ function Capture:open_template(template)
self._close_tmp = utils.open_tmp_org_window(16, config.win_split_mode, config.win_border, on_close)
vim.api.nvim_buf_set_lines(0, 0, -1, true, content)
self.templates:setup()
- vim.api.nvim_buf_set_var(0, 'org_template', template)
- vim.api.nvim_buf_set_var(0, 'org_capture', true)
+
+ vim.b.org_template = template
+ vim.b.org_capture = true
config:setup_mappings('capture')
end
@@ -97,23 +117,22 @@ end
---@param confirm? boolean
function Capture:refile(confirm)
local is_modified = vim.bo.modified
- local file, lines, item, template = self:_get_refile_vars()
- if not file then
+ local opts = self:_get_refile_vars()
+ if not opts.file then
return
end
- local headline_title = template.headline
if confirm and is_modified then
- local choice = vim.fn.confirm(string.format('Do you want to refile this to %s?', file), '&Yes\n&No')
+ local choice = vim.fn.confirm(string.format('Do you want to refile this to %s?', opts.file), '&Yes\n&No')
vim.cmd([[redraw!]])
if choice ~= 1 then
return utils.echo_info('Canceled.')
end
end
vim.defer_fn(function()
- if headline_title then
- self:refile_to_headline(file, lines, item, headline_title)
+ if opts.headline then
+ self:refile_to_headline(opts)
else
- self:_refile_to_end(file, lines, item)
+ self:_refile_to_end(opts)
end
if not confirm then
@@ -124,17 +143,18 @@ end
---Triggered when refiling to destination from capture buffer
function Capture:refile_to_destination()
- local file, lines, item = self:_get_refile_vars()
- if not file then
+ local opts = self:_get_refile_vars()
+ if not opts.file then
return
end
- self:_refile_content_with_fallback(lines, file, item)
+ self:_refile_content_with_fallback(opts)
self:kill()
end
---@private
+---@return CaptureOpts
function Capture:_get_refile_vars()
- local template = vim.api.nvim_buf_get_var(0, 'org_template') or {}
+ local template = vim.b.org_template or {}
local target = self.templates:compile_target(template.target or config.org_default_notes_file)
local file = vim.fn.resolve(vim.fn.fnamemodify(target, ':p'))
@@ -142,7 +162,7 @@ function Capture:_get_refile_vars()
local choice = vim.fn.confirm(('Refile destination %s does not exist. Create now?'):format(file), '&Yes\n&No')
if choice ~= 1 then
utils.echo_error('Cannot proceed without a valid refile destination')
- return
+ return {}
end
vim.fn.mkdir(vim.fn.fnamemodify(file, ':h'), 'p')
vim.fn.writefile({}, file)
@@ -154,48 +174,55 @@ function Capture:_get_refile_vars()
item = org_file:get_headlines()[1]
end
- return file, lines, item, template
+ return {
+ file = file,
+ lines = lines,
+ item = item,
+ template = template,
+ headline = template.headline,
+ }
end
---Triggered from org file when we want to refile headline
function Capture:refile_headline_to_destination()
local destination_file = Files.get_current_file()
local item = destination_file:get_closest_headline()
+ if not item then
+ return
+ end
local lines = destination_file:get_headline_lines(item)
- return self:_refile_content_with_fallback(lines, nil, item)
+ return self:_refile_content_with_fallback({
+ lines = lines,
+ item = item,
+ template = Template:new(),
+ })
end
----@param file File
----@param item string
----@param archive_file string
----@return string
-function Capture:refile_file_headline_to_archive(file, item, archive_file)
- local lines = file:get_headline_lines(item)
- return self:_refile_to_end(archive_file, lines, item, string.format('Archived to %s', archive_file))
+---@param opts CaptureOpts
+---@return boolean
+function Capture:refile_file_headline_to_archive(opts)
+ opts.message = string.format('Archived to %s', opts.file)
+ return self:_refile_to_end(opts)
end
---@private
----@param file string
----@param lines string[]
----@param item? Section
----@param message? string
+---@param opts CaptureOpts
---@return boolean
-function Capture:_refile_to_end(file, lines, item, message)
- local refiled = self:_refile_to(file, lines, item, '$')
+function Capture:_refile_to_end(opts)
+ opts.range = Range.from_line(-1)
+ local refiled = self:_refile_to(opts)
if not refiled then
return false
end
- utils.echo_info(message or string.format('Wrote %s', file))
+ utils.echo_info(opts.message or string.format('Wrote %s', opts.file))
return true
end
---@private
----@param lines string[]
----@param fallback_file string
----@param item? Section
----@return string
-function Capture:_refile_content_with_fallback(lines, fallback_file, item)
- local default_file = fallback_file and fallback_file ~= '' and vim.fn.fnamemodify(fallback_file, ':p') or nil
+---@param opts CaptureOpts
+---@return boolean
+function Capture:_refile_content_with_fallback(opts)
+ local default_file = opts.file and opts.file ~= '' and vim.fn.fnamemodify(opts.file, ':p') or nil
local valid_destinations = {}
for _, file in ipairs(Files.filenames()) do
@@ -210,79 +237,125 @@ function Capture:_refile_content_with_fallback(lines, fallback_file, item)
utils.echo_error(
"'" .. destination[1] .. "' is not a file specified in the 'org_agenda_files' setting. Refiling cancelled."
)
- return
+ return false
end
- return self:_refile_to_end(default_file, lines, item)
+ opts.file = default_file
+ return self:_refile_to_end(opts)
end
- local destination_file = valid_destinations[destination[1]]
- local destination_headline = table.concat({ unpack(destination, 2) }, '/')
- if not destination_headline or destination_headline == '' then
- return self:_refile_to_end(destination_file, lines, item)
+ opts.file = valid_destinations[destination[1]]
+ opts.headline = table.concat({ unpack(destination, 2) }, '/')
+ if not opts.headline or opts.headline == '' then
+ return self:_refile_to_end(opts)
end
- return self:refile_to_headline(destination_file, lines, item, destination_headline)
+ return self:refile_to_headline(opts)
end
----@param destination_filename string
----@param lines string[]
----@param item? Section
----@param headline_title? string
-function Capture:refile_to_headline(destination_filename, lines, item, headline_title)
- local destination_file = Files.get(destination_filename)
+---@param opts CaptureOpts
+function Capture:refile_to_headline(opts)
+ local destination_file = Files.get(opts.file)
local headline
- if headline_title then
- headline = destination_file:find_headline_by_title(headline_title, true)
+ if opts.headline then
+ headline = destination_file:find_headline_by_title(opts.headline, true)
if not headline then
- utils.echo_error(
- "headline '" .. headline_title .. "' does not exist in '" .. destination_filename .. "'. Aborted refiling."
- )
+ utils.echo_error("headline '" .. opts.headline .. "' does not exist in '" .. opts.file .. "'. Aborted refiling.")
return false
end
end
+ local item = opts.item
if item then
-- Refiling in same file just moves the lines from one position
-- to another,so we need to apply demote instantly
local is_same_file = destination_file.filename == item.root.filename
if item.level <= headline.level then
- lines = item:demote(headline.level - item.level + 1, true, not is_same_file)
+ opts.lines = item:demote(headline.level - item.level + 1, true, not is_same_file)
else
- lines = item:promote(item.level - headline.level - 1, true, not is_same_file)
+ opts.lines = item:promote(item.level - headline.level - 1, true, not is_same_file)
end
end
- local refiled = self:_refile_to(destination_filename, lines, item, headline.range.end_line)
+ opts.range = Range.from_line(headline.range.end_line)
+ local refiled = self:_refile_to(opts)
if not refiled then
return false
end
- utils.echo_info(string.format('Wrote %s', destination_filename))
+ utils.echo_info(string.format('Wrote %s', opts.file))
return true
end
+---@param opts CaptureOpts
+local function add_empty_lines(opts)
+ local empty_lines = opts.template.properties.empty_lines
+
+ for _ = 1, empty_lines.before do
+ table.insert(opts.lines, 1, '')
+ end
+
+ for _ = 1, empty_lines.after do
+ table.insert(opts.lines, '')
+ end
+end
+
+---@param opts CaptureOpts
+local function apply_properties(opts)
+ if opts.template then
+ add_empty_lines(opts)
+ end
+end
+
+local function remove_buffer_empty_lines(opts)
+ local line_count = vim.api.nvim_buf_line_count(0)
+ local range = opts.range
+
+ local end_line = range.end_line
+ if end_line < 0 then
+ end_line = end_line + line_count + 1
+ end
+
+ local start_line = end_line - 1
+
+ local is_line_empty = function(row)
+ local line = vim.api.nvim_buf_get_lines(0, row, row + 1, true)[1]
+ line = vim.trim(line)
+ return #line == 0
+ end
+
+ while start_line >= 0 and is_line_empty(start_line) do
+ start_line = start_line - 1
+ end
+ start_line = start_line + 1
+
+ while end_line < line_count and is_line_empty(end_line) do
+ end_line = end_line + 1
+ end
+
+ range.start_line = start_line
+ range.end_line = end_line
+end
+
---@private
----@param file string
----@param lines string[]
----@param item? Section
----@param destination_line string|number
+---@param opts CaptureOpts
---@return boolean
-function Capture:_refile_to(file, lines, item, destination_line)
- if not file then
+function Capture:_refile_to(opts)
+ if not opts.file then
return false
end
- local is_same_file = file == utils.current_file_path()
+ apply_properties(opts)
+
+ local is_same_file = opts.file == utils.current_file_path()
local cur_win = vim.api.nvim_get_current_win()
+ local item = opts.item
if is_same_file and item then
- vim.cmd(
- string.format('silent! %d,%d move %s', item.range.start_line, item.range.end_line, tostring(destination_line))
- )
+ vim.cmd(string.format('silent! %d,%d move %s', item.range.start_line, item.range.end_line, tostring(opts.file)))
return true
end
if not is_same_file then
- local bufnr = vim.fn.bufadd(file)
+ local bufnr = vim.fn.bufadd(opts.file)
vim.api.nvim_open_win(bufnr, true, {
relative = 'editor',
width = 1,
@@ -295,7 +368,10 @@ function Capture:_refile_to(file, lines, item, destination_line)
})
end
- vim.fn.append(destination_line, lines)
+ remove_buffer_empty_lines(opts)
+
+ local range = opts.range
+ vim.api.nvim_buf_set_lines(0, range.start_line, range.end_line, false, opts.lines)
if not is_same_file then
vim.cmd('silent! wq!')
diff --git a/lua/orgmode/capture/template.lua b/lua/orgmode/capture/template.lua
new file mode 100644
index 000000000..636f78f4c
--- /dev/null
+++ b/lua/orgmode/capture/template.lua
@@ -0,0 +1,85 @@
+---@class TemplateEmptyLines
+---@field before integer
+---@field after integer
+local TemplateEmptyLines = {}
+
+function TemplateEmptyLines:new(opts)
+ opts = opts or {}
+
+ vim.validate({
+ before = { opts.before, 'number', true },
+ after = { opts.after, 'number', true },
+ })
+
+ local this = {}
+ this.before = opts.before or 0
+ this.after = opts.after or 0
+
+ setmetatable(this, self)
+ self.__index = self
+ return this
+end
+
+---@class TemplateProperties
+---@field empty_lines TemplateEmptyLines
+local TemplateProperties = {}
+
+function TemplateProperties:new(opts)
+ opts = opts or {}
+
+ vim.validate({
+ empty_lines = { opts.empty_lines, { 'table', 'number' }, true },
+ })
+
+ local empty_lines = opts.empty_lines or {}
+ if type(empty_lines) == 'number' then
+ empty_lines = { before = empty_lines, after = empty_lines }
+ end
+
+ local this = {}
+ this.empty_lines = TemplateEmptyLines:new(empty_lines)
+
+ setmetatable(this, self)
+ self.__index = self
+ return this
+end
+
+---@class Template
+---@field description string
+---@field template string|string[]
+---@field target string?
+---@field headline string?
+---@field properties TemplateProperties
+---@field subtemplates table
+local Template = {}
+
+function Template:new(opts)
+ opts = opts or {}
+
+ vim.validate({
+ description = { opts.description, 'string', true },
+ template = { opts.template, { 'string', 'table' }, true },
+ target = { opts.target, 'string', true },
+ headline = { opts.headline, 'string', true },
+ properties = { opts.properties, 'table', true },
+ subtemplates = { opts.subtemplates, 'table', true },
+ })
+
+ local this = {}
+ this.description = opts.description or ''
+ this.template = opts.template or ''
+ this.target = opts.target
+ this.headline = opts.headline
+ this.properties = TemplateProperties:new(opts.properties)
+
+ this.subtemplates = {}
+ for key, subtemplate in pairs(opts.subtemplates or {}) do
+ this.subtemplates[key] = Template:new(subtemplate)
+ end
+
+ setmetatable(this, self)
+ self.__index = self
+ return this
+end
+
+return Template
diff --git a/lua/orgmode/capture/templates.lua b/lua/orgmode/capture/templates.lua
index 3d0fb69ed..00d5349f8 100644
--- a/lua/orgmode/capture/templates.lua
+++ b/lua/orgmode/capture/templates.lua
@@ -1,6 +1,8 @@
local config = require('orgmode.config')
+local Template = require('orgmode.capture.template')
local Date = require('orgmode.objects.date')
local utils = require('orgmode.utils')
+
local expansions = {
['%f'] = function()
return vim.fn.expand('%')
@@ -34,13 +36,25 @@ local expansions = {
---@see https://orgmode.org/manual/Capture-templates.html
---@class Templates
----@field templates table
+---@field templates table
local Templates = {}
--- TODO Introduce type
-function Templates:new()
+function Templates:new(templates)
local opts = {}
- opts.templates = config.org_capture_templates
+
+ vim.validate({
+ templates = { templates, 'table', true },
+ })
+
+ opts.templates = {}
+ for key, template in pairs(templates or config.org_capture_templates) do
+ if type(template) == 'table' then
+ opts.templates[key] = Template:new(template)
+ else
+ opts.templates[key] = template
+ end
+ end
+
setmetatable(opts, self)
self.__index = self
return opts
diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua
index d13c9216e..9402b7811 100644
--- a/lua/orgmode/org/mappings.lua
+++ b/lua/orgmode/org/mappings.lua
@@ -43,11 +43,19 @@ function OrgMappings:archive()
end
local item = file:get_closest_headline()
local archive_location = file:get_archive_file_location()
+ if not archive_location or not item then
+ return
+ end
+
local archive_directory = vim.fn.fnamemodify(archive_location, ':p:h')
if vim.fn.isdirectory(archive_directory) == 0 then
vim.fn.mkdir(archive_directory, 'p')
end
- self.capture:refile_file_headline_to_archive(file, item, archive_location)
+ self.capture:refile_file_headline_to_archive({
+ file = archive_location,
+ item = item,
+ lines = file:get_headline_lines(item),
+ })
Files.reload(
archive_location,
vim.schedule_wrap(function()
diff --git a/tests/plenary/capture/capture_spec.lua b/tests/plenary/capture/capture_spec.lua
index cfc49c3aa..390d501d9 100644
--- a/tests/plenary/capture/capture_spec.lua
+++ b/tests/plenary/capture/capture_spec.lua
@@ -1,8 +1,13 @@
local Capture = require('orgmode.capture')
+local Templates = require('orgmode.capture.templates')
+local Template = require('orgmode.capture.template')
+local File = require('orgmode.parser.file')
+local helpers = require('tests.plenary.ui.helpers')
+local org = require('orgmode')
describe('Menu Items', function()
it('should create a menu item for each template', function()
- local templates = {
+ local templates = Templates:new({
t = {
description = 'todo',
},
@@ -12,8 +17,8 @@ describe('Menu Items', function()
f = {
description = 'file update',
},
- }
- menu_items = Capture:_create_menu_items(templates)
+ })
+ local menu_items = Capture:_create_menu_items(templates:get_list())
assert.are.same(#menu_items, 3)
assert.are.same(
{ 'b', 'f', 't' },
@@ -24,7 +29,7 @@ describe('Menu Items', function()
end)
it('should create one entry for multi-key shortcuts', function()
- local multikey_templates = {
+ local multikey_templates = Templates:new({
k = 'Multikey templates',
kt = {
description = 'multikey todo',
@@ -36,8 +41,8 @@ describe('Menu Items', function()
kbf = {
description = 'file bookmark',
},
- }
- menu_items = Capture:_create_menu_items(multikey_templates)
+ })
+ local menu_items = Capture:_create_menu_items(multikey_templates:get_list())
assert.are.same(#menu_items, 1)
assert.are.same(
{ 'k' },
@@ -48,7 +53,7 @@ describe('Menu Items', function()
end)
it('computes the sub templates', function()
- local multikey_templates = {
+ local multikey_templates = Templates:new({
k = 'Multikey templates',
kt = {
description = 'multikey todo',
@@ -60,9 +65,9 @@ describe('Menu Items', function()
kbf = {
description = 'file bookmark',
},
- }
- sub_template_items = Capture:_get_subtemplates('k', multikey_templates)
- assert.are.same({
+ })
+ local sub_template_items = Capture:_get_subtemplates('k', multikey_templates:get_list())
+ local expected = Templates:new({
b = 'multikey bookmark',
bb = {
description = 'browser bookmark',
@@ -73,14 +78,248 @@ describe('Menu Items', function()
t = {
description = 'multikey todo',
},
- }, sub_template_items)
+ }):get_list()
+ assert.are.same(expected, sub_template_items)
+ end)
+
+ it('should create one entry for multi-key shortcuts with subtemplates', function()
+ local multikey_templates = Templates:new({
+ k = {
+ description = 'Multikey templates',
+ subtemplates = {
+ t = {
+ description = 'multikey todo',
+ },
+ b = {
+ description = 'multikey bookmark',
+ subtemplates = {
+ b = {
+ description = 'browser bookmark',
+ },
+ f = {
+ description = 'file bookmark',
+ },
+ },
+ },
+ },
+ },
+ })
+ local menu_items = Capture:_create_menu_items(multikey_templates:get_list())
+ assert.are.same(#menu_items, 1)
+ assert.are.same(
+ { 'k' },
+ vim.tbl_map(function(x)
+ return x.key
+ end, vim.fn.sort(menu_items))
+ )
end)
it('adds an ellipses', function()
- local template = {
+ local templates = Templates:new({
k = 'Multikey template',
- }
- menu_item = Capture:_create_menu_items(template)
+ })
+ local menu_item = Capture:_create_menu_items(templates:get_list())
assert.are.same('Multikey template...', menu_item[1].label)
end)
end)
+
+describe('Refile', function()
+ it('to empty file', function()
+ local destination_file = helpers.load_file_content({})
+
+ local capture_lines = { '* foo' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:_refile_to_end({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '* foo',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+
+ it('to end', function()
+ local destination_file = helpers.load_file_content({
+ '* foobar',
+ ' ',
+ '',
+ '\t\t\t\t',
+ '',
+ })
+
+ local capture_lines = { '** baz' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:_refile_to_end({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '* foobar',
+ '** baz',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+ it('to headline', function()
+ local destination_file = helpers.load_file_content({
+ '* foobar',
+ ' ',
+ '',
+ '\t\t\t\t',
+ '',
+ '* barbar',
+ '',
+ ' ',
+ '',
+ })
+
+ local capture_lines = { '** baz' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:refile_to_headline({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ headline = 'foobar',
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '* foobar',
+ '** baz',
+ '* barbar',
+ '',
+ ' ',
+ '',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+end)
+
+describe('Refile with empty lines', function()
+ it('to empty file', function()
+ local destination_file = helpers.load_file_content({})
+
+ local capture_lines = { '* foo' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:_refile_to_end({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ template = Template:new({
+ properties = {
+ empty_lines = {
+ before = 2,
+ after = 1,
+ },
+ },
+ }),
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '',
+ '',
+ '* foo',
+ '',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+
+ it('to end', function()
+ local destination_file = helpers.load_file_content({
+ '* foobar',
+ ' ',
+ '',
+ '\t\t\t\t',
+ '',
+ })
+
+ local capture_lines = { '** baz' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:_refile_to_end({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ template = Template:new({
+ properties = {
+ empty_lines = {
+ before = 2,
+ after = 1,
+ },
+ },
+ }),
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '* foobar',
+ '',
+ '',
+ '** baz',
+ '',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+ it('to headline', function()
+ local destination_file = helpers.load_file_content({
+ '* foobar',
+ ' ',
+ '',
+ '\t\t\t\t',
+ '',
+ '* barbar',
+ '',
+ ' ',
+ '',
+ })
+
+ local capture_lines = { '** baz' }
+ helpers.load_file_content(capture_lines)
+ local capture_file = File.from_content(capture_lines, 'capture')
+ assert(capture_file)
+ local item = capture_file:get_headlines()[1]
+
+ org.instance().capture:refile_to_headline({
+ file = destination_file,
+ lines = capture_lines,
+ item = item,
+ headline = 'foobar',
+ template = Template:new({
+ properties = {
+ empty_lines = {
+ before = 2,
+ after = 1,
+ },
+ },
+ }),
+ })
+ vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
+ assert.are.same({
+ '* foobar',
+ '',
+ '',
+ '** baz',
+ '',
+ '* barbar',
+ '',
+ ' ',
+ '',
+ }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
+ end)
+end)
diff --git a/tests/plenary/ui/mappings/refile_spec.lua b/tests/plenary/ui/mappings/refile_spec.lua
index 07db96153..76ae2fdfb 100644
--- a/tests/plenary/ui/mappings/refile_spec.lua
+++ b/tests/plenary/ui/mappings/refile_spec.lua
@@ -22,7 +22,12 @@ describe('Refile mappings', function()
source_file = Files.get_current_file()
local item = source_file:get_closest_headline()
- org.instance().capture:refile_to_headline(destination_file, source_file:get_headline_lines(item), item, 'foo')
+ org.instance().capture:refile_to_headline({
+ file = destination_file,
+ lines = source_file:get_headline_lines(item),
+ item = item,
+ headline = 'foo',
+ })
assert.are.same('* not to be refiled', vim.fn.getline(1))
vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
assert.are.same({
@@ -47,7 +52,12 @@ describe('Refile mappings', function()
source_file = Files.get_current_file()
local item = source_file:get_closest_headline()
- org.instance().capture:refile_to_headline(destination_file, source_file:get_headline_lines(item), item, 'foobar')
+ org.instance().capture:refile_to_headline({
+ file = destination_file,
+ lines = source_file:get_headline_lines(item),
+ item = item,
+ headline = 'foobar',
+ })
assert.are.same('* not to be refiled', vim.fn.getline(1))
vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
assert.are.same({
@@ -72,7 +82,12 @@ describe('Refile mappings', function()
source_file = Files.get_current_file()
local item = source_file:get_closest_headline()
- org.instance().capture:refile_to_headline(destination_file, source_file:get_headline_lines(item), item, 'foobar')
+ org.instance().capture:refile_to_headline({
+ file = destination_file,
+ lines = source_file:get_headline_lines(item),
+ item = item,
+ headline = 'foobar',
+ })
assert.are.same('* not to be refiled', vim.fn.getline(1))
vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
assert.are.same({