Skip to content

Commit e129aab

Browse files
authored
fix: promote headline while refile to file-end (#641)
* fix: promote headline while refile to file-end - promote headlines to first level when archiving (like Emacs) - promote headlines to first level when refiling without targeting a headline (Emacs only allows headlines as refiling targets) Motivation: Refiling to file-ends is only indirectly used in Emacs Org-Mode for archiving. In this case Org-Mode promotes headlines to the first level in the archive file. This fix aligns the archiving-behavior with Emacs while also adapting the normal refiling feature when no headline was given. This has two advantages: It prevents the user from getting wrong and unexpected structures in org-files and archive-files and it aligns Nvim-Orgmode more with the original Emacs Org-Mode. * chore: remove superfluous refile functions Because the main logic is now consolidated in Capture:_refile_to, the two functions Capture:refile_to_headline and Capture:_refile_to_end are not needed anymore. Adjust the tests accordingly. Note: A general refactoring of the Capture class would be helpful in the future. The current Capture class mixes several concerns: Refiling, capturing and archiving. Separating these into different classes might help to make the code easier to understand and to adjust. --------- Co-authored-by: Sebastian Flügge <[email protected]>
1 parent 3a5148f commit e129aab

File tree

3 files changed

+53
-67
lines changed

3 files changed

+53
-67
lines changed

lua/orgmode/capture/init.lua

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,7 @@ function Capture:refile(confirm)
129129
end
130130
end
131131
vim.defer_fn(function()
132-
if opts.headline then
133-
self:refile_to_headline(opts)
134-
else
135-
self:_refile_to_end(opts)
136-
end
132+
self:_refile_to(opts)
137133

138134
if not confirm then
139135
self:kill()
@@ -202,20 +198,7 @@ end
202198
---@return boolean
203199
function Capture:refile_file_headline_to_archive(opts)
204200
opts.message = string.format('Archived to %s', opts.file)
205-
return self:_refile_to_end(opts)
206-
end
207-
208-
---@private
209-
---@param opts CaptureOpts
210-
---@return boolean
211-
function Capture:_refile_to_end(opts)
212-
opts.range = Range.from_line(-1)
213-
local refiled = self:_refile_to(opts)
214-
if not refiled then
215-
return false
216-
end
217-
utils.echo_info(opts.message or string.format('Wrote %s', opts.file))
218-
return true
201+
return self:_refile_to(opts)
219202
end
220203

221204
---@private
@@ -240,49 +223,27 @@ function Capture:_refile_content_with_fallback(opts)
240223
return false
241224
end
242225
opts.file = default_file
243-
return self:_refile_to_end(opts)
226+
return self:_refile_to(opts)
244227
end
245228

246229
opts.file = valid_destinations[destination[1]]
247230
opts.headline = table.concat({ unpack(destination, 2) }, '/')
248-
if not opts.headline or opts.headline == '' then
249-
return self:_refile_to_end(opts)
250-
end
251-
return self:refile_to_headline(opts)
231+
return self:_refile_to(opts)
252232
end
253233

254-
---@param opts CaptureOpts
255-
function Capture:refile_to_headline(opts)
256-
local destination_file = Files.get(opts.file)
257-
local headline
258-
if opts.headline then
259-
headline = destination_file:find_headline_by_title(opts.headline, true)
260-
261-
if not headline then
262-
utils.echo_error("headline '" .. opts.headline .. "' does not exist in '" .. opts.file .. "'. Aborted refiling.")
263-
return false
264-
end
265-
end
266-
267-
local item = opts.item
268-
if item then
269-
-- Refiling in same file just moves the lines from one position
270-
-- to another,so we need to apply demote instantly
271-
local is_same_file = destination_file.filename == item.root.filename
272-
if item.level <= headline.level then
273-
opts.lines = item:demote(headline.level - item.level + 1, true, not is_same_file)
274-
else
275-
opts.lines = item:promote(item.level - headline.level - 1, true, not is_same_file)
276-
end
234+
---@param item Section
235+
---@param target_level integer
236+
---@param is_same_file boolean
237+
function Capture:_adapt_headline_level(item, target_level, is_same_file)
238+
-- Refiling in same file just moves the lines from one position
239+
-- to another,so we need to apply demote instantly
240+
if target_level == 0 then
241+
return item:promote(item.level - 1, true, not is_same_file)
277242
end
278-
279-
opts.range = Range.from_line(headline.range.end_line)
280-
local refiled = self:_refile_to(opts)
281-
if not refiled then
282-
return false
243+
if item.level <= target_level then
244+
return item:demote(target_level - item.level + 1, true, not is_same_file)
283245
end
284-
utils.echo_info(string.format('Wrote %s', opts.file))
285-
return true
246+
return item:promote(item.level - target_level - 1, true, not is_same_file)
286247
end
287248

288249
---@param opts CaptureOpts
@@ -343,16 +304,40 @@ function Capture:_refile_to(opts)
343304
return false
344305
end
345306

346-
apply_properties(opts)
307+
local has_headline = opts.headline and opts.headline ~= ''
308+
local destination_file = Files.get(opts.file)
309+
local target_level = 0
310+
local target_line = -1
311+
local should_adapt_headline = has_headline or (opts.item ~= nil and opts.item.level > 1)
312+
if has_headline then
313+
local headline = destination_file:find_headline_by_title(opts.headline, true)
314+
if not headline then
315+
utils.echo_error("headline '" .. opts.headline .. "' does not exist in '" .. opts.file .. "'. Aborted refiling.")
316+
return false
317+
end
318+
target_level = headline.level
319+
target_line = headline.range.end_line
320+
end
347321

348322
local is_same_file = opts.file == utils.current_file_path()
349-
350323
local item = opts.item
324+
if item and should_adapt_headline then
325+
-- Refiling in same file just moves the lines from one position
326+
-- to another,so we need to apply demote instantly
327+
opts.lines = self:_adapt_headline_level(item, target_level, is_same_file)
328+
end
329+
330+
opts.range = Range.from_line(target_line)
331+
332+
apply_properties(opts)
333+
351334
if is_same_file and item then
352335
local target = opts.range.end_line
353336
local view = vim.fn.winsaveview()
354337
vim.cmd(string.format('silent! %d,%d move %s', item.range.start_line, item.range.end_line, target))
355338
vim.fn.winrestview(view)
339+
340+
utils.echo_info(opts.message or string.format('Wrote %s', opts.file))
356341
return true
357342
end
358343

@@ -375,6 +360,7 @@ function Capture:_refile_to(opts)
375360
pcall(vim.api.nvim_buf_set_lines, 0, item.range.start_line - 1, item.range.end_line, false, {})
376361
end
377362

363+
utils.echo_info(opts.message or string.format('Wrote %s', opts.file))
378364
return true
379365
end
380366

tests/plenary/capture/capture_spec.lua

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe('Refile', function()
133133
assert(capture_file)
134134
local item = capture_file:get_headlines()[1]
135135

136-
org.instance().capture:_refile_to_end({
136+
org.instance().capture:_refile_to({
137137
file = destination_file,
138138
lines = capture_lines,
139139
item = item,
@@ -159,15 +159,15 @@ describe('Refile', function()
159159
assert(capture_file)
160160
local item = capture_file:get_headlines()[1]
161161

162-
org.instance().capture:_refile_to_end({
162+
org.instance().capture:_refile_to({
163163
file = destination_file,
164164
lines = capture_lines,
165165
item = item,
166166
})
167167
vim.cmd('edit' .. vim.fn.fnameescape(destination_file))
168168
assert.are.same({
169169
'* foobar',
170-
'** baz',
170+
'* baz',
171171
}, vim.api.nvim_buf_get_lines(0, 0, -1, false))
172172
end)
173173
it('to headline', function()
@@ -189,7 +189,7 @@ describe('Refile', function()
189189
assert(capture_file)
190190
local item = capture_file:get_headlines()[1]
191191

192-
org.instance().capture:refile_to_headline({
192+
org.instance().capture:_refile_to({
193193
file = destination_file,
194194
lines = capture_lines,
195195
item = item,
@@ -217,7 +217,7 @@ describe('Refile with empty lines', function()
217217
assert(capture_file)
218218
local item = capture_file:get_headlines()[1]
219219

220-
org.instance().capture:_refile_to_end({
220+
org.instance().capture:_refile_to({
221221
file = destination_file,
222222
lines = capture_lines,
223223
item = item,
@@ -254,7 +254,7 @@ describe('Refile with empty lines', function()
254254
assert(capture_file)
255255
local item = capture_file:get_headlines()[1]
256256

257-
org.instance().capture:_refile_to_end({
257+
org.instance().capture:_refile_to({
258258
file = destination_file,
259259
lines = capture_lines,
260260
item = item,
@@ -272,7 +272,7 @@ describe('Refile with empty lines', function()
272272
'* foobar',
273273
'',
274274
'',
275-
'** baz',
275+
'* baz',
276276
'',
277277
}, vim.api.nvim_buf_get_lines(0, 0, -1, false))
278278
end)
@@ -295,7 +295,7 @@ describe('Refile with empty lines', function()
295295
assert(capture_file)
296296
local item = capture_file:get_headlines()[1]
297297

298-
org.instance().capture:refile_to_headline({
298+
org.instance().capture:_refile_to({
299299
file = destination_file,
300300
lines = capture_lines,
301301
item = item,

tests/plenary/ui/mappings/refile_spec.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Refile mappings', function()
2222

2323
source_file = Files.get_current_file()
2424
local item = source_file:get_closest_headline()
25-
org.instance().capture:refile_to_headline({
25+
org.instance().capture:_refile_to({
2626
file = destination_file,
2727
lines = source_file:get_headline_lines(item),
2828
item = item,
@@ -52,7 +52,7 @@ describe('Refile mappings', function()
5252

5353
source_file = Files.get_current_file()
5454
local item = source_file:get_closest_headline()
55-
org.instance().capture:refile_to_headline({
55+
org.instance().capture:_refile_to({
5656
file = destination_file,
5757
lines = source_file:get_headline_lines(item),
5858
item = item,
@@ -82,7 +82,7 @@ describe('Refile mappings', function()
8282

8383
source_file = Files.get_current_file()
8484
local item = source_file:get_closest_headline()
85-
org.instance().capture:refile_to_headline({
85+
org.instance().capture:_refile_to({
8686
file = destination_file,
8787
lines = source_file:get_headline_lines(item),
8888
item = item,

0 commit comments

Comments
 (0)