Skip to content

Commit a464d0e

Browse files
authored
Merge pull request #141 from lambdalisue/new-rename
Support complex renaming via 'rename' action of 'file' or 'dict' scheme
2 parents 1f19a80 + fbff24c commit a464d0e

File tree

9 files changed

+422
-144
lines changed

9 files changed

+422
-144
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
let s:ESCAPE_PATTERN = '^$~.*[]\'
2+
3+
function! fern#internal#rename_solver#solve(pairs, ...) abort
4+
let options = extend({
5+
\ 'exist': { p -> getftype(p) !=# '' },
6+
\ 'tempname': { _ -> tempname() },
7+
\ 'isdirectory': { p -> isdirectory(p) },
8+
\}, a:0 ? a:1 : {},
9+
\)
10+
let Exist = options.exist
11+
let Tempname = options.tempname
12+
let IsDirectory = options.isdirectory
13+
" Sort by 'dst' depth
14+
let pairs = sort(copy(a:pairs), funcref('s:compare'))
15+
" Build steps from given pairs
16+
let steps = []
17+
let tears = []
18+
let src_map = s:dict(map(
19+
\ copy(a:pairs),
20+
\ { -> [v:val[0], 1] },
21+
\))
22+
for [src, dst] in pairs
23+
let rsrc_map = s:dict(map(
24+
\ copy(a:pairs),
25+
\ { -> [s:replace(v:val[0], steps), 1] },
26+
\))
27+
let rsrc = s:replace(src, steps)
28+
if rsrc ==# dst
29+
continue
30+
endif
31+
let rdst = s:replace_backword(dst, steps)
32+
if get(rsrc_map, dst)
33+
let tmp = Tempname(dst)
34+
call add(steps, [rsrc, tmp, '', ''])
35+
call add(tears, [tmp, dst, src, rdst])
36+
elseif !get(src_map, rdst) && Exist(rdst)
37+
throw printf('Destination "%s" already exist as "%s"', dst, rdst)
38+
else
39+
call add(steps, [rsrc, dst, src, rdst])
40+
endif
41+
endfor
42+
let steps += tears
43+
" Check 'dst' uniqueness
44+
let dup = s:find_duplication(map(copy(steps), { -> v:val[1] }))
45+
if !empty(dup)
46+
throw printf('Destination "%s" appears more than once', dup)
47+
endif
48+
" Check parent directories of 'dst'
49+
for [rsrc, dst, src, rdst] in steps
50+
let prv = rdst
51+
let cur = fern#internal#path#dirname(prv)
52+
while cur !=# '' && cur !=# prv
53+
if !Exist(cur)
54+
break
55+
elseif !IsDirectory(cur)
56+
throw printf(
57+
\ 'Destination "%s" in "%s" is not directory',
58+
\ s:replace(cur, steps),
59+
\ dst,
60+
\)
61+
endif
62+
let prv = cur
63+
let cur = fern#internal#path#dirname(cur)
64+
endwhile
65+
endfor
66+
return map(steps, { -> v:val[0:1] })
67+
endfunction
68+
69+
function! s:dict(entries) abort
70+
let m = {}
71+
call map(copy(a:entries), { _, v -> extend(m, { v[0]: v[1] }) })
72+
return m
73+
endfunction
74+
75+
function! s:compare(a, b) abort
76+
let a = len(split(a:a[1], '[/\\]'))
77+
let b = len(split(a:b[1], '[/\\]'))
78+
return a is# b ? 0 : a > b ? 1 : -1
79+
endfunction
80+
81+
function! s:find_duplication(list) abort
82+
let seen = {}
83+
for item in a:list
84+
if has_key(seen, item)
85+
return item
86+
endif
87+
let seen[item] = 1
88+
endfor
89+
endfunction
90+
91+
function! s:replace(text, applied) abort
92+
let text = a:text
93+
for item in a:applied
94+
let [src, dst] = item[0:1]
95+
let text = substitute(text, escape(src, s:ESCAPE_PATTERN), dst, '')
96+
endfor
97+
return text
98+
endfunction
99+
100+
function! s:replace_backword(text, applied) abort
101+
let text = a:text
102+
for item in reverse(copy(a:applied))
103+
let [src, dst] = item[0:1]
104+
let text = substitute(text, escape(dst, s:ESCAPE_PATTERN), src, '')
105+
endfor
106+
return text
107+
endfunction

autoload/fern/internal/renamer.vim

Lines changed: 0 additions & 119 deletions
This file was deleted.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
let s:Promise = vital#fern#import('Async.Promise')
2+
let s:ESCAPE_PATTERN = '^$~.*[]\'
3+
4+
function! fern#internal#replacer#start(factory, ...) abort
5+
let options = extend({
6+
\ 'bufname': printf('fern-replacer:%s', sha256(localtime()))[:7],
7+
\ 'opener': 'vsplit',
8+
\ 'cursor': [1, 1],
9+
\ 'is_drawer': v:false,
10+
\ 'modifiers': [],
11+
\}, a:0 ? a:1 : {},
12+
\)
13+
return s:Promise.new(funcref('s:executor', [a:factory, options]))
14+
endfunction
15+
16+
function! s:executor(factory, options, resolve, reject) abort
17+
call fern#internal#buffer#open(a:options.bufname, {
18+
\ 'opener': a:options.opener,
19+
\ 'locator': a:options.is_drawer,
20+
\ 'keepalt': !a:options.is_drawer && g:fern#keepalt_on_edit,
21+
\ 'keepjumps': !a:options.is_drawer && g:fern#keepjumps_on_edit,
22+
\ 'mods': 'noautocmd',
23+
\})
24+
25+
setlocal buftype=acwrite bufhidden=wipe
26+
setlocal noswapfile nobuflisted
27+
setlocal nowrap
28+
setlocal filetype=fern-replacer
29+
30+
let b:fern_replacer_resolve = a:resolve
31+
let b:fern_replacer_factory = a:factory
32+
let b:fern_replacer_candidates = a:factory()
33+
let b:fern_replacer_modifiers = a:options.modifiers
34+
35+
augroup fern_replacer_internal
36+
autocmd! * <buffer>
37+
autocmd BufReadCmd <buffer> call s:BufReadCmd()
38+
autocmd BufWriteCmd <buffer> call s:BufWriteCmd()
39+
autocmd ColorScheme <buffer> call s:highlight()
40+
augroup END
41+
42+
call s:highlight()
43+
call s:syntax()
44+
45+
" Do NOT allow to add/remove lines
46+
nnoremap <buffer><silent> <Plug>(fern-replacer-p) :<C-u>call <SID>map_paste(0)<CR>
47+
nnoremap <buffer><silent> <Plug>(fern-replacer-P) :<C-u>call <SID>map_paste(-1)<CR>
48+
nnoremap <buffer><silent> <Plug>(fern-replacer-warn) :<C-u>call <SID>map_warn()<CR>
49+
inoremap <buffer><silent><expr> <Plug>(fern-replacer-warn) <SID>map_warn()
50+
nnoremap <buffer><silent> dd 0D
51+
nmap <buffer> p <Plug>(fern-replacer-p)
52+
nmap <buffer> P <Plug>(fern-replacer-P)
53+
nmap <buffer> o <Plug>(fern-replacer-warn)
54+
nmap <buffer> O <Plug>(fern-replacer-warn)
55+
imap <buffer> <C-m> <Plug>(fern-replacer-warn)
56+
imap <buffer> <Return> <Plug>(fern-replacer-warn)
57+
edit
58+
call cursor(a:options.cursor)
59+
endfunction
60+
61+
function! s:map_warn() abort
62+
echohl WarningMsg
63+
echo 'Newline is prohibited in the replacer buffer'
64+
echohl None
65+
return ''
66+
endfunction
67+
68+
function! s:map_paste(offset) abort
69+
let line = getline('.')
70+
let v = substitute(getreg(), '\r\?\n', '', 'g')
71+
let c = col('.') + a:offset - 1
72+
let l = line[:c]
73+
let r = line[c + 1:]
74+
call setline(line('.'), l . v . r)
75+
endfunction
76+
77+
function! s:BufReadCmd() abort
78+
let b:fern_replacer_candidates = b:fern_replacer_factory()
79+
call s:syntax()
80+
call setline(1, b:fern_replacer_candidates)
81+
endfunction
82+
83+
function! s:BufWriteCmd() abort
84+
if !&modifiable
85+
return
86+
endif
87+
let candidates = b:fern_replacer_candidates
88+
let result = []
89+
for index in range(len(candidates))
90+
let src = candidates[index]
91+
let dst = getline(index + 1)
92+
if empty(dst) || dst ==# src
93+
continue
94+
endif
95+
call add(result, [src, dst])
96+
endfor
97+
try
98+
for Modifier in b:fern_replacer_modifiers
99+
let result = Modifier(result)
100+
endfor
101+
let Resolve = b:fern_replacer_resolve
102+
set nomodified
103+
close
104+
call Resolve(result)
105+
catch
106+
echohl ErrorMsg
107+
echo '[fern] Please fix the following error first to continue or cancel with ":q!"'
108+
echo printf('[fern] %s', substitute(v:exception, '^Vim(.*):', '', ''))
109+
echohl None
110+
endtry
111+
endfunction
112+
113+
function! s:syntax() abort
114+
syntax clear
115+
syntax match FernReplacerModified '^.\+$'
116+
117+
for index in range(len(b:fern_replacer_candidates))
118+
let candidate = b:fern_replacer_candidates[index]
119+
execute printf(
120+
\ 'syntax match FernReplacerOriginal ''^\%%%dl%s$''',
121+
\ index + 1,
122+
\ escape(candidate, s:ESCAPE_PATTERN),
123+
\)
124+
endfor
125+
endfunction
126+
127+
function! s:highlight() abort
128+
highlight default link FernReplacerOriginal Normal
129+
highlight default link FernReplacerModified Special
130+
endfunction

0 commit comments

Comments
 (0)