From 1400955226d06cb24a1e3f4f76fd630d8f08c4c9 Mon Sep 17 00:00:00 2001 From: Eugen Date: Sat, 28 Dec 2019 10:46:28 +0300 Subject: [PATCH 1/9] Implement configurable middleware stack (#161) --- spec/vimrc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/vimrc diff --git a/spec/vimrc b/spec/vimrc new file mode 100644 index 000000000..4c39116c8 --- /dev/null +++ b/spec/vimrc @@ -0,0 +1,21 @@ +set runtimepath+=. +set runtimepath+=~/.cache/esearch-dev/plugins/vader.vim/ +set runtimepath+=~/.cache/esearch-dev/plugins/vim-visual-multi/ +set runtimepath+=~/.cache/esearch-dev/plugins/vim-fugitive/ +set runtimepath+=~/.cache/esearch-dev/plugins/nerdtree/ +set runtimepath+=~/.cache/esearch-dev/plugins/vim-dirvish/ +set runtimepath+=~/.cache/esearch-dev/plugins/vim-netranger/ +set runtimepath+=~/.cache/esearch-dev/plugins/fern.vim/ +set runtimepath+=~/.cache/esearch-dev/plugins/defx.nvim/ +if has('nvim') + " TODO + " call remote#host#RegisterPlugin('python3', $HOME.'/.cache/esearch-dev/plugins/defx.nvim/rplugin/python3/defx', [ + " \ {'sync': v:true, 'name': '_defx_init', 'type': 'function', 'opts': {}}, + " \ ]) +else + set runtimepath+=~/.cache/esearch-dev/plugins/nvim-yarp/ + set runtimepath+=~/.cache/esearch-dev/plugins/vim-hug-neovim-rpc/ +endif + +filetype plugin indent on +syntax enable From 4302489847bc24ecf7b0866c2dbe21ef6390cc48 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Tue, 17 Nov 2020 14:04:29 +0300 Subject: [PATCH 2/9] fix README.md typos --- spec/vimrc | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 spec/vimrc diff --git a/spec/vimrc b/spec/vimrc deleted file mode 100644 index 4c39116c8..000000000 --- a/spec/vimrc +++ /dev/null @@ -1,21 +0,0 @@ -set runtimepath+=. -set runtimepath+=~/.cache/esearch-dev/plugins/vader.vim/ -set runtimepath+=~/.cache/esearch-dev/plugins/vim-visual-multi/ -set runtimepath+=~/.cache/esearch-dev/plugins/vim-fugitive/ -set runtimepath+=~/.cache/esearch-dev/plugins/nerdtree/ -set runtimepath+=~/.cache/esearch-dev/plugins/vim-dirvish/ -set runtimepath+=~/.cache/esearch-dev/plugins/vim-netranger/ -set runtimepath+=~/.cache/esearch-dev/plugins/fern.vim/ -set runtimepath+=~/.cache/esearch-dev/plugins/defx.nvim/ -if has('nvim') - " TODO - " call remote#host#RegisterPlugin('python3', $HOME.'/.cache/esearch-dev/plugins/defx.nvim/rplugin/python3/defx', [ - " \ {'sync': v:true, 'name': '_defx_init', 'type': 'function', 'opts': {}}, - " \ ]) -else - set runtimepath+=~/.cache/esearch-dev/plugins/nvim-yarp/ - set runtimepath+=~/.cache/esearch-dev/plugins/vim-hug-neovim-rpc/ -endif - -filetype plugin indent on -syntax enable From 16bf55b4db803bc9ad00dd3574c310b745b67082 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Tue, 17 Nov 2020 16:10:09 +0300 Subject: [PATCH 3/9] fix middleware.vader tests verbosity --- autoload/esearch/middleware/splice_pattern.vim | 1 - 1 file changed, 1 deletion(-) diff --git a/autoload/esearch/middleware/splice_pattern.vim b/autoload/esearch/middleware/splice_pattern.vim index 7ff056f7c..09246b41a 100644 --- a/autoload/esearch/middleware/splice_pattern.vim +++ b/autoload/esearch/middleware/splice_pattern.vim @@ -1,5 +1,4 @@ fu! esearch#middleware#splice_pattern#apply(esearch) abort call a:esearch.pattern.splice(a:esearch) - let a:esearch.last_pattern = a:esearch.pattern return a:esearch endfu From 3e665a00d3be062d7b03d165c7ceeee394941e30 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Tue, 17 Nov 2020 16:31:36 +0300 Subject: [PATCH 4/9] fix commandline specs --- autoload/esearch/middleware/splice_pattern.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/esearch/middleware/splice_pattern.vim b/autoload/esearch/middleware/splice_pattern.vim index 09246b41a..7ff056f7c 100644 --- a/autoload/esearch/middleware/splice_pattern.vim +++ b/autoload/esearch/middleware/splice_pattern.vim @@ -1,4 +1,5 @@ fu! esearch#middleware#splice_pattern#apply(esearch) abort call a:esearch.pattern.splice(a:esearch) + let a:esearch.last_pattern = a:esearch.pattern return a:esearch endfu From 118b937f0850321a2b3f8ef94f2bca2d58798b15 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Thu, 25 Feb 2021 16:19:26 +0300 Subject: [PATCH 5/9] implemnt rename and it's undo --- autoload/esearch/buf.vim | 11 +- autoload/esearch/out/win/diff.vim | 55 +++++--- autoload/esearch/out/win/modifiable.vim | 23 ++-- autoload/esearch/shell.vim | 5 + autoload/esearch/util.vim | 5 + autoload/esearch/writer.vim | 68 ++++++++-- spec/helper.vader | 41 +++--- spec/rename.vader | 166 ++++++++++++++++++++++++ spec/support/helpers/open.rb | 40 ------ 9 files changed, 317 insertions(+), 97 deletions(-) create mode 100644 spec/rename.vader delete mode 100644 spec/support/helpers/open.rb diff --git a/autoload/esearch/buf.vim b/autoload/esearch/buf.vim index 3982cbd95..ec75b24a7 100644 --- a/autoload/esearch/buf.vim +++ b/autoload/esearch/buf.vim @@ -117,7 +117,14 @@ fu! esearch#buf#rename_qf(name) abort let w:quickfix_title = a:name endfu -fu! s:bufdo(bufnr, cmd, bang) abort + +fu! esearch#buf#handle() abort + return s:Handle +endfu + +let s:Handle = {} + +fu! s:Handle.bufdo(cmd, ...) abort dict let cur_buffer = esearch#buf#stay() try exe (bufnr('%') == a:bufnr ? '' : a:bufnr.'bufdo ') . a:cmd . (a:bang ? '!' : '') @@ -222,5 +229,5 @@ fu! s:CurrentBufferGuard.new() abort dict endfu fu! s:CurrentBufferGuard.restore() abort dict - if self.bufnr != bufnr('') | exe self.bufnr 'buffer!' | endif + if self.bufnr != bufnr('') | noau exe self.bufnr 'buffer!' | endif endfu diff --git a/autoload/esearch/out/win/diff.vim b/autoload/esearch/out/win/diff.vim index 40c41205a..5183040b2 100644 --- a/autoload/esearch/out/win/diff.vim +++ b/autoload/esearch/out/win/diff.vim @@ -1,9 +1,14 @@ -let s:Dict = vital#esearch#import('Data.Dict') -let s:by_key = function('esearch#util#by_key') +let s:Dict = vital#esearch#import('Data.Dict') +let s:by_key = function('esearch#util#by_key') +let s:Filepath = vital#esearch#import('System.Filepath') +let s:String = vital#esearch#import('Data.String') + +let s:path_separator = s:Filepath.separator() let s:broken_entry_fmt = 'Unexpected entry format at line %d. Must match /^ (sign)? (line_number) (text)/.' let s:broken_header_fmt = 'Broken header at line %d.' let s:unexpected_filename_fmt = 'Unexpected filename at line %d. Each filename must be preceded with a blank line separator.' +let s:unexpected_slash_fmt = 'Unexpected trailing "'.s:path_separator.'" in the filename at line %d. Forgot to insert a basename?' let s:unexpected_prepend_fmt = 'Unexpected "^" at line %d. Prepended lines must be placed before the base or appended lines.' let s:unexpected_append_fmt = 'Unexpected "+" at line %d. Appended lines must be placed after the base line.' let s:duplicate_set_fmt = 'Duplicate line number at line %d.' @@ -14,13 +19,13 @@ let s:unexpected_separator_fmt = 'Unexpected blank line parator at line %d.' let s:unexpected_sign_fmt = 'Unexpected sign at line %d.' fu! esearch#out#win#diff#do() abort - let stats = {'deleted': 0, 'modified': 0, 'added': 0, 'files': 0} + let stats = {'deleted': 0, 'modified': 0, 'added': 0, 'files': 0, 'renamed': 0} let diffs = {'by_id': {}, 'stats': stats} let iter = s:DiffsIterator.new(getline(1, '$'), b:esearch, stats) while iter.has_next() let diff = iter.next() - if !empty(diff.edits) + if !empty(diff) let diffs.by_id[diff.ctx.id] = diff let stats.files += 1 endif @@ -70,10 +75,10 @@ fu! s:DiffsIterator.next_deleted() abort dict let ctx = self.contexts[id] let [edits, deleted_lines_a] = [{}, copy(ctx.lines)] let [begin, lnums_b, texts_b] = [-1, [], []] - return s:Diff.new(self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b) + return s:Diff.new(self.stats, self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b) endfu -fu! s:DiffsIterator.next_modified() abort +fu! s:DiffsIterator.next_modified() abort dict let [filename_b, lnum_was, sign_was] = ['', -1, ''] " backtrack one line back let [edits, deleted_lines_a, begin, lnums_b, texts_b] = [{}, {}, -1, [], []] @@ -83,7 +88,7 @@ fu! s:DiffsIterator.next_modified() abort if empty(line) if empty(filename_b) | throw s:err(s:unexpected_separator_fmt, self.wlnum) | endif let self.wlnum += 1 - return s:Diff.new(self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b) + return s:Diff.new(self.stats, self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b, filename_b) endif if line[0] ==# ' ' @@ -135,9 +140,12 @@ fu! s:DiffsIterator.next_modified() abort let ctx = self.contexts[self.state[self.wlnum]] silent! unlet self.deleted_ctxs_a[ctx.id] - if !empty(self.lines[self.wlnum - 1]) || filename_b !=# fnameescape(ctx.filename) + if !empty(self.lines[self.wlnum - 1]) throw s:err(s:unexpected_filename_fmt, self.wlnum) endif + if s:String.ends_with(filename_b, s:path_separator) + throw s:err(s:unexpected_slash_fmt, self.wlnum) + endif let lines_a = ctx.lines let deleted_lines_a = copy(lines_a) @@ -147,7 +155,7 @@ fu! s:DiffsIterator.next_modified() abort let self.wlnum += 1 endwhile - return s:Diff.new(self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b) + return s:Diff.new(self.stats, self.add_deletes(edits, deleted_lines_a), begin, ctx, lnums_b, texts_b, filename_b) endfu fu! s:DiffsIterator.add_deletes(edits, deleted_lines_a) abort dict @@ -164,19 +172,32 @@ endfu let s:Diff = {} -fu! s:Diff.new(edits, begin, ctx, lnums_b, texts_b) abort - if empty(a:edits) | return {'edits': []} | endif - let edits = s:reverse_flatten(a:edits) - let win_undos = s:win_undos(a:ctx.lines, a:ctx, a:ctx.begin, a:lnums_b) - let [win_edits, lines_b] = s:win_write_post_edits(edits, a:lnums_b, a:texts_b, a:begin) - return { +fu! s:Diff.new(stats, edits, begin, ctx, lnums_b, texts_b, ...) abort dict + let diff = {} + + if a:0 && simplify(fnameescape(a:ctx.filename)) !=# simplify(a:1) + let diff.filename = esearch#shell#fnameunescape(a:1) + let a:stats.renamed += 1 + endif + + if empty(a:edits) + if empty(diff) | return diff | endif + + let [edits, win_undos, win_edits, lines_b] = [[], [], [], a:ctx.lines] + else + let edits = s:reverse_flatten(a:edits) + let win_undos = s:win_undos(a:ctx.lines, a:ctx, a:ctx.begin, a:lnums_b) + let [win_edits, lines_b] = s:win_write_post_edits(edits, a:lnums_b, a:texts_b, a:begin) + endif + + return extend(diff, { \ 'ctx': a:ctx, + \ 'begin': a:begin, \ 'edits': edits, \ 'win_edits': win_edits, \ 'win_undos': win_undos, - \ 'begin': a:begin, \ 'lines_b': lines_b, - \} + \}) endfu fu! s:win_write_post_edits(edits, lnums_b, texts_b, begin) abort diff --git a/autoload/esearch/out/win/modifiable.vim b/autoload/esearch/out/win/modifiable.vim index d4f7e5e2f..f0eacc7bb 100644 --- a/autoload/esearch/out/win/modifiable.vim +++ b/autoload/esearch/out/win/modifiable.vim @@ -55,16 +55,23 @@ fu! s:write_cmd() abort return s:Log.error(substitute(v:exception, '^DiffError:', '', '')) endtry - if diff.stats.files == 0 | echo 'Nothing to save' | return | endi - - let [kinds, total_changes] = [[], diff.stats.modified + diff.stats.deleted + diff.stats.added] - if diff.stats.added > 0 | let kinds += [diff.stats.added . ' added'] | endif - if diff.stats.modified > 0 | let kinds += [diff.stats.modified . ' modified'] | endif - if diff.stats.deleted > 0 | let kinds += [diff.stats.deleted . ' deleted'] | endif + if diff.stats.files == 0 | echo 'Nothing to save' | return | endif + + let stats = diff.stats + let [kinds, total_changes] = [[], stats.modified + stats.deleted + stats.added] + if stats.added > 0 | let kinds += [stats.added . ' added'] | endif + if stats.modified > 0 | let kinds += [stats.modified . ' modified'] | endif + if stats.deleted > 0 | let kinds += [stats.deleted . ' deleted'] | endif + if stats.renamed > 0 + let renamed = ', ' . stats.renamed . ' renamed ' . (stats.renamed == 1 ? 'file' : 'files') + else + let renamed = '' + endif - let message = printf('Write changes? (%s %s in %d %s)', + let message = printf('Write changes? (%s %s in %d %s%s)', \ join(kinds, ', '), total_changes == 1 ? 'line' : 'lines', - \ diff.stats.files, diff.stats.files == 1 ? 'file' : 'files') + \ stats.files, stats.files == 1 ? 'file' : 'files', + \ renamed) if !get(g:, 'esearch_yes') && confirm(message, "&Yes\n&Cancel") != 1 | return |endif call esearch#writer#do(diff, b:esearch, v:cmdbang) diff --git a/autoload/esearch/shell.vim b/autoload/esearch/shell.vim index 0a370cf4b..0293f4158 100644 --- a/autoload/esearch/shell.vim +++ b/autoload/esearch/shell.vim @@ -229,7 +229,12 @@ elseif g:esearch#has#vms else let s:path_esc_chars = " \t\n*?[{`$\\%#'\"|!<" endif +let s:path_unesc_re = '\\\(' . join(map(split(s:path_esc_chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')'), '\|') . '\)' fu! s:fnameescape(string) abort return escape(a:string, s:metachars . s:path_esc_chars) endfu + +fu! esearch#shell#fnameunescape(fname) abort + return substitute(a:fname, s:path_unesc_re, '\1', 'g') +endfu diff --git a/autoload/esearch/util.vim b/autoload/esearch/util.vim index e10d7dd54..455024b67 100644 --- a/autoload/esearch/util.vim +++ b/autoload/esearch/util.vim @@ -304,3 +304,8 @@ endfu fu! esearch#util#noop(...) abort return 0 endfu + +fu! esearch#util#file_exists(path) abort + let [dirname, basename] = [s:Filepath.dirname(a:path), s:Filepath.basename(a:path)] + return !empty(globpath(dirname, basename, 1)) +endfu diff --git a/autoload/esearch/writer.vim b/autoload/esearch/writer.vim index 481f0f466..396dcac3c 100644 --- a/autoload/esearch/writer.vim +++ b/autoload/esearch/writer.vim @@ -16,6 +16,7 @@ fu! s:Writer.new(diffs, esearch) abort dict \ 'search_buf': s:Buf.for(a:esearch.bufnr), \ 'win_edits': [], \ 'win_undos': [], + \ 'renames': [], \ 'conflicts': [], \}) endfu @@ -48,13 +49,18 @@ fu! s:Writer.write(bang) abort dict if !self.verify_not_modified(diff, buf) | continue | endif endif - let self.win_edits = diff.win_edits + self.win_edits - let self.win_undos = self.update_file(buf, diff) + self.win_undos - let self.esearch.contexts[diff.ctx.id].lines = diff.lines_b - let self.esearch.contexts[diff.ctx.id].begin = diff.begin + let self.win_edits = diff.win_edits + self.win_edits + let self.win_undos = self.update_file(buf, diff) + self.win_undos + let contexts[diff.ctx.id].lines = diff.lines_b + let contexts[diff.ctx.id].begin = diff.begin + + if has_key(diff, 'filename') + call add(self.renames, [buf, contexts[diff.ctx.id], diff.filename]) + endif if !empty(WriteCb) | call WriteCb(buf, a:bang) | endif endfor + call self.rename(a:bang) finally au! esearch_write call current_window.restore() @@ -68,6 +74,43 @@ fu! s:Writer.write(bang) abort dict call esearch#util#try_defer(function('esearch#util#doautocmd'), 'User esearch_write_post') endfu +fu! s:Writer.rename(bang) abort + for [buf, ctx_a, relative_filename_b] in self.renames + let filename_a = simplify(esearch#util#abspath(self.esearch.cwd, ctx_a.filename)) + let filename_b = simplify(esearch#util#abspath(self.esearch.cwd, relative_filename_b)) + let filename_a = s:Prelude.substitute_path_separator(filename_a) + let dir_b = s:Prelude.substitute_path_separator(fnamemodify(filename_b, ':h')) + let filename_b = s:Prelude.substitute_path_separator(filename_b) + + if isdirectory(filename_b) + call add(self.conflicts, [relative_filename_b, "can't overwite the directory"]) + continue + endif + + if !a:bang && esearch#util#file_exists(filename_b) + if !has_key(g:, 'esearch_overwrite') + \ && confirm(filename_b . ' exists. Overwrite?', "&Yes\n&Cancel") == 2 + \ || !g:esearch_overwrite + continue + endif + endif + + call mkdir(dir_b, 'p') + if rename(filename_a, filename_b) !=# 0 + call add(self.conflicts, [relative_filename_b, 'rename() has failed']) + continue + endif + + if bufloaded(buf.bufnr) + let hidden = esearch#let#restorable({'&l:hidden': 1}) + silent call buf.bufdo('silent! saveas! '.fnameescape(filename_b) . '| bdelete! # ') + call hidden.restore() + endif + + let ctx_a.filename = relative_filename_b + endfor +endfu + fu! s:Writer.create_undo_entry() abort dict let original_register = @s call self.search_buf.goto() @@ -96,6 +139,8 @@ fu! s:Writer.update_file(buf, diff) abort call a:buf.goto() let edits = a:diff.edits + if empty(edits) | return a:diff.win_undos | endif + for edit in edits[:-2] call call(a:buf[edit.func], edit.args) endfor @@ -116,7 +161,7 @@ fu! s:Writer.update_win(win_edits) abort endfu fu! s:Writer.handle_existing_swap(path) abort dict - call add(self.conflicts, {'filename': a:path, 'reason': 'swapfile exists'}) + call add(self.conflicts, [a:path, 'swapfile exists']) " Ensure the buffer is deleted after quitting the swap prompt if bufexists(a:path) @@ -134,17 +179,17 @@ fu! s:Writer.log() abort dict return setbufvar(self.esearch.bufnr, '&modified', 0) end - let reasons = map(self.conflicts, 'printf("\n %s (%s)", v:val.filename, v:val.reason)') - let message = "Can't write changes to the following files:".join(reasons, '') + let reasons = map(self.conflicts, 'printf("\n %s (%s)", v:val[0], v:val[1])') + let message = "Can't apply changes for following files:".join(reasons, '') call esearch#util#warn(message) endfu fu! s:Writer.verify_readable(diff, path) abort dict if filereadable(a:path) | return 1 | endif if get(a:diff.ctx, 'rev') - call add(self.conflicts, {'filename': a:path, 'reason': 'is a git blob'}) + call add(self.conflicts, [a:path, 'is a git blob']) else - call add(self.conflicts, {'filename': a:path, 'reason': 'is not readable'}) + call add(self.conflicts, [a:path, 'is not readable']) endif return 0 endfu @@ -156,10 +201,7 @@ fu! s:Writer.verify_not_modified(diff, buf) abort dict continue endif - call add(self.conflicts, { - \ 'filename': a:diff.ctx.filename, - \ 'reason': 'line '.edit.args[0].' has changed', - \}) + call add(self.conflicts, [a:diff.ctx.filename, 'line '.edit.args[0].' has changed']) return 0 endfor diff --git a/spec/helper.vader b/spec/helper.vader index 8c9b93974..d6526aad4 100644 --- a/spec/helper.vader +++ b/spec/helper.vader @@ -29,28 +29,35 @@ Execute(global setup): endfu fu! UndotreeIsConsistent() abort - let lines = getline(1, '$') - let g:state = [0] - let id = 0 - if lines !=# [''] - if get(lines, 0, '') =~# '^Matches' | let g:state += [0] | endif - if get(lines, 1, 0) is# '' | let g:state += [0] | endif + try + let lines = getline(1, '$') + let g:state = [0] + let id = 0 + if lines !=# [''] + if get(lines, 0, '') =~# '^Matches' | let g:state += [0] | endif + if get(lines, 1, 0) is# '' | let g:state += [0] | endif - for line in lines[2:] - if line =~# g:esearch#out#win#filename_re - let id = filter(copy(b:esearch.contexts), 'fnameescape(v:val.filename) == line')[0].id - endif - call add(g:state, id) - endfor - endif + for line in lines[2:] + if line =~# g:esearch#out#win#filename_re + let id = filter(copy(b:esearch.contexts), 'fnameescape(v:val.filename) == line')[0].id + endif + call add(g:state, id) + endfor + endif - let g:state = map(g:state, '+v:val') - let head_state = map(copy(b:esearch.undotree.head.state), '+v:val') - return head_state ==# g:state && (len(head_state) - 1 == line('$') || head_state == [0]) + let g:state = map(g:state, '+v:val') + let head_state = map(copy(b:esearch.undotree.head.state), '+v:val') + return head_state ==# g:state && (len(head_state) - 1 == line('$') || head_state == [0]) + catch + let g:exception = v:exception + return 0 + endtry endfu fu! UndotreeIsInconsistentMsg() abort - return 'undotree: '.string(g:state).string(b:esearch.state) + let exception = get(g:, 'exception') + unlet! g:exception + return 'undotree: '.string(g:state).string(b:esearch.state).string(exception) endfu " linear congruential generator diff --git a/spec/rename.vader b/spec/rename.vader new file mode 100644 index 000000000..01a7f4c5f --- /dev/null +++ b/spec/rename.vader @@ -0,0 +1,166 @@ +Include: helper.vader + +Before: + let g:esearch.cwd = 'spec/fixtures/rename'.g:test_number.next().'/' + let g:esearch.pattern = 'l\d' + let g:content = ['l1', 'l2'] + let g:file_a = g:esearch.cwd.'file.txt' + let g:file_b = g:esearch.cwd.'elif.txt' + exe 'bwipe' Fixture(g:file_a, g:content) + Save g:esearch_overwrite +After: + Restore g:esearch_overwrite + silent! call delete(g:file_a) + silent! call delete(g:file_b) + silent! call delete(g:file_b, 'd') + +Execute (Rename non-opened): + call esearch#init() + exe "norm /file.txt\ciwelif" + write +Then: + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | write + AssertEqual readfile(g:file_a), g:content + Assert !filereadable(g:file_b) + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + +Execute (Rename opened with :split): + call esearch#init() + exe "norm S" + exe "norm /file.txt\ciwelif" + write +Then: + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + AssertEqual getbufline(g:file_b, 1, '$'), g:content + undo | write + AssertEqual readfile(g:file_a), g:content + AssertEqual getbufline(g:file_a, 1, '$'), g:content + Assert !filereadable(g:file_b) + undo | write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + AssertEqual getbufline(g:file_b, 1, '$'), g:content + +Execute (Rename opened and then closed): + call esearch#init() + exe "norm S" + exe "norm /file.txt\ciwelif" + write +Then: + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + AssertEqual getbufline(g:file_b, 1, '$'), g:content + exe bufwinnr(g:file_b) 'close' + undo | write + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) + Assert !filereadable(g:file_b) + undo | write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_b) + +Execute (Rename into nonexisting directory): + call esearch#init() + let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' + exe "norm /file.txt\ccelif/nested/dir.txt" + write +Then: + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | write + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + Assert !filereadable(g:file_b) + undo | write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + +Execute (Rename with overwriting a file using :write!): + let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' + exe 'bwipe' Fixture(g:file_b, 'contentb') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir.txt" +Then: + write! + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | write + Assert !filereadable(g:file_b) " TODO can be fixed in the future + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + +Execute (Rename with overwriting a file and say (N)o): + let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' + exe 'bwipe' Fixture(g:file_b, 'contentb') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir.txt" +Then: + let g:esearch_overwrite = 0 + write + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + AssertEqual readfile(g:file_b), ['contentb'] + +Execute (Rename with overwriting a file and say (Y)es): + let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' + exe 'bwipe' Fixture(g:file_b, 'contentb') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir.txt" +Then: + let g:esearch_overwrite = 1 + write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | write + Assert !filereadable(g:file_b) " TODO can be fixed in the future + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + + +Execute (Rename with overwriting a dir using :write!): + let g:file_b = g:esearch.cwd.'elif/nested/dir' + call mkdir(g:file_b, 'p') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir" +Then: + write! + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" + +Execute (Rename with overwriting a dir and say (N)o): + let g:file_b = g:esearch.cwd.'elif/nested/dir' + call mkdir(g:file_b, 'p') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir" +Then: + let g:esearch_overwrite = 0 + write + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" + +Execute (Rename with overwriting a dir and say (Y)es): + let g:file_b = g:esearch.cwd.'elif/nested/dir' + call mkdir(g:file_b, 'p') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir" +Then: + let g:esearch_overwrite = 1 + write + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" diff --git a/spec/support/helpers/open.rb b/spec/support/helpers/open.rb deleted file mode 100644 index 730ed284c..000000000 --- a/spec/support/helpers/open.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -# For testing opening entries of both window and quickfix output - -module Helpers::Open - extend RSpec::Matchers::DSL - - def tabpage_buffers_list - editor.echo func('tabpagebuflist') - end - - def tabpage_windows_list - (1..editor.echo(func('tabpagewinnr', func('tabpagenr'), '$'))).to_a - end - - def tabpages_list - (1..editor.echo(func('tabpagenr', '$'))).to_a - end - - def start_editing(path) - change { editor.current_buffer_name } - .to(path.to_s) - end - - def windows - editor.echo func('nvim_list_wins') - end - - def open_window(path) - change { editor.current_buffer_name } - .to end_with path.to_s - end - - def open_tab(path) - change { tabpages_list.count } - .by(1) - .and change { editor.current_buffer_name } - .to end_with path - end -end From 27d8e5c3113699bc8932c395617ae3de5df58a97 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Sat, 3 Oct 2020 14:28:46 +0300 Subject: [PATCH 6/9] fix specs --- autoload/esearch/buf.vim | 35 ++++++-------- autoload/esearch/out/win.vim | 1 + autoload/esearch/out/win/diff.vim | 2 +- autoload/esearch/out/win/update.vim | 1 + autoload/esearch/util.vim | 11 +++++ autoload/esearch/writer.vim | 71 +++++++++++++++++++++++------ spec/rename.vader | 46 ++++++++++++------- 7 files changed, 116 insertions(+), 51 deletions(-) diff --git a/autoload/esearch/buf.vim b/autoload/esearch/buf.vim index ec75b24a7..4bfc841a1 100644 --- a/autoload/esearch/buf.vim +++ b/autoload/esearch/buf.vim @@ -117,23 +117,6 @@ fu! esearch#buf#rename_qf(name) abort let w:quickfix_title = a:name endfu - -fu! esearch#buf#handle() abort - return s:Handle -endfu - -let s:Handle = {} - -fu! s:Handle.bufdo(cmd, ...) abort dict - let cur_buffer = esearch#buf#stay() - try - exe (bufnr('%') == a:bufnr ? '' : a:bufnr.'bufdo ') . a:cmd . (a:bang ? '!' : '') - return 1 - catch | call esearch#util#warn(v:exception) | return 0 - finally | call cur_buffer.restore() - endtry -endfu - fu! esearch#buf#import() abort return copy(s:Buf) endfu @@ -162,6 +145,16 @@ else endfu endif +fu! s:Buf.bufdo(cmd, ...) abort dict + let cur_buffer = esearch#buf#stay() + try + exe (bufnr('%') == self.bufnr ? '' : self.bufnr.'bufdo ') . a:cmd . (a:0 && a:1 ? '!' : '') + return 1 + catch | call esearch#util#warn(v:exception) | return 0 + finally | call cur_buffer.restore() + endtry +endfu + if exists('*nvim_buf_line_count') fu! s:Buf.oneliner() abort dict return nvim_buf_line_count(self.bufnr) == 1 @@ -177,7 +170,7 @@ else endif fu! s:Buf.goto() abort dict - exe 'buffer!' self.bufnr + exe 'buffer!' self.bufnr endfu fu! s:Buf.getline(lnum) abort dict @@ -202,7 +195,7 @@ fu! s:Buf.deleteline(lnum) abort dict endfu fu! s:Buf.write(bang) dict abort - return s:bufdo(self.bufnr, 'write', a:bang) + return self.bufdo('write', a:bang) endfu fu! s:Buf.open(opener, ...) dict abort @@ -211,11 +204,11 @@ fu! s:Buf.open(opener, ...) dict abort endfu fu! s:Buf.bdelete(...) dict abort - return s:bufdo(self.bufnr, 'bdelete', get(a:, 1)) + return self.bufdo('bdelete', get(a:, 1)) endfu fu! s:Buf.bwipeout(...) dict abort - return s:bufdo(self.bufnr, 'bwipeout', get(a:, 1)) + return self.bufdo('bwipeout', get(a:, 1)) endfu fu! esearch#buf#stay() abort diff --git a/autoload/esearch/out/win.vim b/autoload/esearch/out/win.vim index c458ca35f..1a169a70c 100644 --- a/autoload/esearch/out/win.vim +++ b/autoload/esearch/out/win.vim @@ -31,6 +31,7 @@ let g:esearch#out#win#filename_re = '^[^ ]' let g:esearch#out#win#separator_re = '^$' let g:esearch#out#win#linenr_fmt = ' %3d ' let g:esearch#out#win#entry_fmt = ' %3d %s' +let g:esearch#out#win#entry_with_sign_fmt = ' %s %3d %s' let g:esearch#out#win#searches_with_stopped_highlights = esearch#cache#expiring#new({'max_age': 120, 'size': 1024}) diff --git a/autoload/esearch/out/win/diff.vim b/autoload/esearch/out/win/diff.vim index 5183040b2..ad9ea95a0 100644 --- a/autoload/esearch/out/win/diff.vim +++ b/autoload/esearch/out/win/diff.vim @@ -79,7 +79,7 @@ fu! s:DiffsIterator.next_deleted() abort dict endfu fu! s:DiffsIterator.next_modified() abort dict - let [filename_b, lnum_was, sign_was] = ['', -1, ''] " backtrack one line back + let [filename_b, lnum_was, sign_was] = ['', -1, ''] " look one line back let [edits, deleted_lines_a, begin, lnums_b, texts_b] = [{}, {}, -1, [], []] while self.wlnum < len(self.lines) diff --git a/autoload/esearch/out/win/update.vim b/autoload/esearch/out/win/update.vim index 2fd5efc2d..b5ac466bd 100644 --- a/autoload/esearch/out/win/update.vim +++ b/autoload/esearch/out/win/update.vim @@ -189,4 +189,5 @@ fu! esearch#out#win#update#finish(bufnr) abort cal setbufvar(a:bufnr, '&modified', 0) cal esearch#out#win#modifiable#init() if es.win_ui_nvim_syntax | cal luaeval('esearch.buf_attach_ui()') | en + let es.linecount = line('$') endfu diff --git a/autoload/esearch/util.vim b/autoload/esearch/util.vim index 455024b67..918bb318e 100644 --- a/autoload/esearch/util.vim +++ b/autoload/esearch/util.vim @@ -309,3 +309,14 @@ fu! esearch#util#file_exists(path) abort let [dirname, basename] = [s:Filepath.dirname(a:path), s:Filepath.basename(a:path)] return !empty(globpath(dirname, basename, 1)) endfu + +fu! esearch#util#list2dict(list) abort + let [i, dict] = [0, {}] + + while i < len(a:list) + let dict[i + 1] = a:list[i] + let i += 1 + endwhile + + return dict +endfu diff --git a/autoload/esearch/writer.vim b/autoload/esearch/writer.vim index 396dcac3c..59b271ab4 100644 --- a/autoload/esearch/writer.vim +++ b/autoload/esearch/writer.vim @@ -1,3 +1,4 @@ +let s:Prelude = vital#esearch#import('Prelude') let s:Filepath = vital#esearch#import('System.Filepath') let s:List = vital#esearch#import('Data.List') let s:Buf = esearch#buf#import() @@ -11,17 +12,20 @@ let s:Writer = {} fu! s:Writer.new(diffs, esearch) abort dict return extend(deepcopy(self), { - \ 'diffs': a:diffs, - \ 'esearch': a:esearch, - \ 'search_buf': s:Buf.for(a:esearch.bufnr), - \ 'win_edits': [], - \ 'win_undos': [], - \ 'renames': [], - \ 'conflicts': [], + \ 'diffs': a:diffs, + \ 'esearch': a:esearch, + \ 'search_buf': s:Buf.for(a:esearch.bufnr), + \ 'win_edits': [], + \ 'win_undos': [], + \ 'state_undos': [], + \ 'renames': [], + \ 'conflicts': [], \}) endfu fu! s:Writer.write(bang) abort dict + let contexts = self.esearch.contexts + let linecount = self.esearch.linecount let l:WriteCb = self.esearch.write_cb let [current_window, current_buffer, view] = [esearch#win#stay(), esearch#buf#stay(), winsaveview()] @@ -75,6 +79,9 @@ fu! s:Writer.write(bang) abort dict endfu fu! s:Writer.rename(bang) abort + let contexts = self.esearch.contexts + let linecount = self.esearch.linecount + for [buf, ctx_a, relative_filename_b] in self.renames let filename_a = simplify(esearch#util#abspath(self.esearch.cwd, ctx_a.filename)) let filename_b = simplify(esearch#util#abspath(self.esearch.cwd, relative_filename_b)) @@ -87,11 +94,18 @@ fu! s:Writer.rename(bang) abort continue endif - if !a:bang && esearch#util#file_exists(filename_b) - if !has_key(g:, 'esearch_overwrite') - \ && confirm(filename_b . ' exists. Overwrite?', "&Yes\n&Cancel") == 2 - \ || !g:esearch_overwrite - continue + if esearch#util#file_exists(filename_b) + if !a:bang + if has_key(g:, 'esearch_overwrite') + if !g:esearch_overwrite | continue | endif + else + if confirm(filename_b . ' exists. Overwrite?', "&Yes\n&Cancel") == 2 | continue | endif + endif + endif + + if filereadable(filename_b) + let lines_b = readfile(filename_b) + let lines_a = readfile(filename_a) endif endif @@ -107,8 +121,39 @@ fu! s:Writer.rename(bang) abort call hidden.restore() endif + if exists('lines_b') + let fmt = g:esearch#out#win#entry_with_sign_fmt + let i = 0 + while i < len(lines_b) + let lines_b[i] = printf(fmt, '', i + 1, lines_b[i]) + let i += 1 + endw + let lines_b = ['', fnameescape(relative_filename_b)] + lines_b + + call esearch#out#win#update#add_context(self.esearch.contexts, relative_filename_b, linecount + 2, 0) + let linecount += len(lines_b) + let self.esearch.contexts[-1].lines = esearch#util#list2dict(lines_a) + let self.esearch.contexts[-1].end = linecount + let self.win_undos += [{'func': 'appendline', 'args': ['$', lines_b]}] + + let ids = [self.esearch.contexts[-2].id] + repeat([self.esearch.contexts[-1].id], len(lines_b) - 1) + let self.state_undos += [{'func': 'extend', 'args': [ids]}] + + unlet lines_b + endif + let ctx_a.filename = relative_filename_b endfor + let self.esearch.linecount = linecount +endfu + +fu! s:Writer.update_state(state, edits) abort + let state = a:state + + for edit in a:edits + call call(edit.func, [state] + edit.args) + endfor + return state endfu fu! s:Writer.create_undo_entry() abort dict @@ -121,7 +166,7 @@ fu! s:Writer.create_undo_entry() abort dict exe 'undo' self.esearch.undotree.written.changenr call self.update_win(self.win_undos) - let undone_state = self.esearch.undotree.written.state + let undone_state = self.update_state(self.esearch.undotree.written.state, self.state_undos) call self.esearch.undotree.squash(undone_state) call esearch#util#squash_undo() keepjumps %delete _ diff --git a/spec/rename.vader b/spec/rename.vader index 01a7f4c5f..197a4d802 100644 --- a/spec/rename.vader +++ b/spec/rename.vader @@ -9,6 +9,7 @@ Before: exe 'bwipe' Fixture(g:file_a, g:content) Save g:esearch_overwrite After: + messages clear Restore g:esearch_overwrite silent! call delete(g:file_a) silent! call delete(g:file_b) @@ -113,22 +114,22 @@ Then: Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) AssertEqual readfile(g:file_b), ['contentb'] -Execute (Rename with overwriting a file and say (Y)es): - let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' - exe 'bwipe' Fixture(g:file_b, 'contentb') - call esearch#init() - exe "norm /file.txt\ccelif/nested/dir.txt" -Then: - let g:esearch_overwrite = 1 - write - Assert !filereadable(g:file_a) - AssertEqual readfile(g:file_b), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | write - Assert !filereadable(g:file_b) " TODO can be fixed in the future - AssertEqual readfile(g:file_a), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - +" Execute (Rename with overwriting a file and say (Y)es): +" let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' +" exe 'bwipe' Fixture(g:file_b, 'contentb') +" call esearch#init() +" exe "norm /file.txt\ccelif/nested/dir.txt" +" Then: +" let g:esearch_overwrite = 1 +" write +" Assert !filereadable(g:file_a) +" AssertEqual readfile(g:file_b), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) +" Log execute('fu {458}') +" undo | write +" Assert !filereadable(g:file_b) " TODO can be fixed in the future +" AssertEqual readfile(g:file_a), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) Execute (Rename with overwriting a dir using :write!): let g:file_b = g:esearch.cwd.'elif/nested/dir' @@ -164,3 +165,16 @@ Then: AssertEqual readfile(g:file_a), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" + +Execute (Rename deleted after the search is done): + let g:file_b = g:esearch.cwd.'elif/nested/dir' + call mkdir(g:file_b, 'p') + call esearch#init() + call delete(g:file_a) + exe "norm /file.txt\ccelif/nested/dir" +Then: + let g:esearch_overwrite = 1 + write + Assert !filereadable(g:file_a) && !filereadable(g:file_b) + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + Assert split(execute('messages'), "\n")[-1] =~# "is not readable" From 1aefcd1d96f7cc9209c60e1992b19f1d09d8653f Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Thu, 25 Feb 2021 17:46:51 +0300 Subject: [PATCH 7/9] fix specs with overwriting --- autoload/esearch/writer.vim | 17 +++++++------ spec/rename.vader | 51 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/autoload/esearch/writer.vim b/autoload/esearch/writer.vim index 59b271ab4..9688bec55 100644 --- a/autoload/esearch/writer.vim +++ b/autoload/esearch/writer.vim @@ -117,26 +117,27 @@ fu! s:Writer.rename(bang) abort if bufloaded(buf.bufnr) let hidden = esearch#let#restorable({'&l:hidden': 1}) - silent call buf.bufdo('silent! saveas! '.fnameescape(filename_b) . '| bdelete! # ') + silent call buf.bufdo('silent! saveas! ' . fnameescape(filename_b) . '| bdelete! # ') call hidden.restore() endif if exists('lines_b') let fmt = g:esearch#out#win#entry_with_sign_fmt let i = 0 - while i < len(lines_b) - let lines_b[i] = printf(fmt, '', i + 1, lines_b[i]) + let win_lines_b = copy(lines_b) + while i < len(win_lines_b) + let win_lines_b[i] = printf(fmt, '', i + 1, win_lines_b[i]) let i += 1 endw - let lines_b = ['', fnameescape(relative_filename_b)] + lines_b + let win_lines_b = ['', fnameescape(relative_filename_b)] + win_lines_b call esearch#out#win#update#add_context(self.esearch.contexts, relative_filename_b, linecount + 2, 0) - let linecount += len(lines_b) - let self.esearch.contexts[-1].lines = esearch#util#list2dict(lines_a) + let linecount += len(win_lines_b) + let self.esearch.contexts[-1].lines = esearch#util#list2dict(lines_b) let self.esearch.contexts[-1].end = linecount - let self.win_undos += [{'func': 'appendline', 'args': ['$', lines_b]}] + let self.win_undos += [{'func': 'appendline', 'args': ['$', win_lines_b]}] - let ids = [self.esearch.contexts[-2].id] + repeat([self.esearch.contexts[-1].id], len(lines_b) - 1) + let ids = [self.esearch.contexts[-2].id] + repeat([self.esearch.contexts[-1].id], len(win_lines_b) - 1) let self.state_undos += [{'func': 'extend', 'args': [ids]}] unlet lines_b diff --git a/spec/rename.vader b/spec/rename.vader index 197a4d802..fc3d91407 100644 --- a/spec/rename.vader +++ b/spec/rename.vader @@ -23,11 +23,11 @@ Then: Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | write + undo | doau TextChanged | write AssertEqual readfile(g:file_a), g:content Assert !filereadable(g:file_b) Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | write + undo | doau TextChanged | write Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) @@ -41,11 +41,11 @@ Then: Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content AssertEqual getbufline(g:file_b, 1, '$'), g:content - undo | write + undo | doau TextChanged | write AssertEqual readfile(g:file_a), g:content AssertEqual getbufline(g:file_a, 1, '$'), g:content Assert !filereadable(g:file_b) - undo | write + undo | doau TextChanged | write Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content AssertEqual getbufline(g:file_b, 1, '$'), g:content @@ -60,11 +60,11 @@ Then: AssertEqual readfile(g:file_b), g:content AssertEqual getbufline(g:file_b, 1, '$'), g:content exe bufwinnr(g:file_b) 'close' - undo | write + undo | doau TextChanged | write AssertEqual readfile(g:file_a), g:content Assert !bufloaded(g:file_a) Assert !filereadable(g:file_b) - undo | write + undo | doau TextChanged | write Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_b) @@ -78,11 +78,11 @@ Then: Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | write + undo | doau TextChanged | write AssertEqual readfile(g:file_a), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) Assert !filereadable(g:file_b) - undo | write + undo | doau TextChanged | write Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) @@ -97,8 +97,8 @@ Then: Assert !filereadable(g:file_a) AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | write - Assert !filereadable(g:file_b) " TODO can be fixed in the future + undo | doau TextChanged | write + " Assert filereadable(g:file_b), 'TODO can be fixed in the future' AssertEqual readfile(g:file_a), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) @@ -114,22 +114,21 @@ Then: Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) AssertEqual readfile(g:file_b), ['contentb'] -" Execute (Rename with overwriting a file and say (Y)es): -" let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' -" exe 'bwipe' Fixture(g:file_b, 'contentb') -" call esearch#init() -" exe "norm /file.txt\ccelif/nested/dir.txt" -" Then: -" let g:esearch_overwrite = 1 -" write -" Assert !filereadable(g:file_a) -" AssertEqual readfile(g:file_b), g:content -" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) -" Log execute('fu {458}') -" undo | write -" Assert !filereadable(g:file_b) " TODO can be fixed in the future -" AssertEqual readfile(g:file_a), g:content -" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) +Execute (Rename with overwriting a file and say (Y)es): + let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' + exe 'bwipe' Fixture(g:file_b, 'contentb') + call esearch#init() + exe "norm /file.txt\ccelif/nested/dir.txt" +Then: + let g:esearch_overwrite = 1 + write + Assert !filereadable(g:file_a) + AssertEqual readfile(g:file_b), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + undo | doau TextChanged | write + Assert !filereadable(g:file_b), "TODO can be fixed in the future" + AssertEqual readfile(g:file_a), g:content + Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) Execute (Rename with overwriting a dir using :write!): let g:file_b = g:esearch.cwd.'elif/nested/dir' From 4e4a422c70632746c706e5a893f831d6bac5c3a5 Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Thu, 25 Feb 2021 18:34:27 +0300 Subject: [PATCH 8/9] disallow overriding existing files --- autoload/esearch/writer.vim | 104 +++++++++++++++++++++--------------- spec/rename.vader | 87 +++++++++++++++--------------- 2 files changed, 106 insertions(+), 85 deletions(-) diff --git a/autoload/esearch/writer.vim b/autoload/esearch/writer.vim index 9688bec55..ab211434a 100644 --- a/autoload/esearch/writer.vim +++ b/autoload/esearch/writer.vim @@ -30,10 +30,10 @@ fu! s:Writer.write(bang) abort dict let [current_window, current_buffer, view] = [esearch#win#stay(), esearch#buf#stay(), winsaveview()] call esearch#util#doautocmd('User esearch_write_pre') - " Wipeout preview buffer if was viewed ignoring swap to prevent missing - " swapexists messages later + " Wipeout preview buffer if was viewed ignoring swap to prevent missing + " swapexists messages later call esearch#preview#wipeout() - aug esearch_write + aug __esearch_write__ au! au SwapExists * let v:swapchoice = 'q' aug END @@ -64,9 +64,10 @@ fu! s:Writer.write(bang) abort dict if !empty(WriteCb) | call WriteCb(buf, a:bang) | endif endfor + call self.rename(a:bang) finally - au! esearch_write + au! __esearch_write__ call current_window.restore() call current_buffer.restore() call self.create_undo_entry() @@ -81,19 +82,15 @@ endfu fu! s:Writer.rename(bang) abort let contexts = self.esearch.contexts let linecount = self.esearch.linecount + let edits = [] - for [buf, ctx_a, relative_filename_b] in self.renames + for [buf_a, ctx_a, relative_filename_b] in self.renames let filename_a = simplify(esearch#util#abspath(self.esearch.cwd, ctx_a.filename)) let filename_b = simplify(esearch#util#abspath(self.esearch.cwd, relative_filename_b)) let filename_a = s:Prelude.substitute_path_separator(filename_a) let dir_b = s:Prelude.substitute_path_separator(fnamemodify(filename_b, ':h')) let filename_b = s:Prelude.substitute_path_separator(filename_b) - if isdirectory(filename_b) - call add(self.conflicts, [relative_filename_b, "can't overwite the directory"]) - continue - endif - if esearch#util#file_exists(filename_b) if !a:bang if has_key(g:, 'esearch_overwrite') @@ -103,49 +100,72 @@ fu! s:Writer.rename(bang) abort endif endif + if isdirectory(filename_b) + return add(self.conflicts, [relative_filename_b, "can't overwite the directory"]) + endif if filereadable(filename_b) - let lines_b = readfile(filename_b) - let lines_a = readfile(filename_a) + " TODO edits applying orders when :undo is used + return add(self.conflicts, [relative_filename_b, "can't overwite the file"]) + let overrided_lines_b = readfile(filename_b) endif endif - call mkdir(dir_b, 'p') - if rename(filename_a, filename_b) !=# 0 - call add(self.conflicts, [relative_filename_b, 'rename() has failed']) - continue - endif - - if bufloaded(buf.bufnr) - let hidden = esearch#let#restorable({'&l:hidden': 1}) - silent call buf.bufdo('silent! saveas! ' . fnameescape(filename_b) . '| bdelete! # ') - call hidden.restore() + let edits += [{'func': 'mkdir', 'args': [dir_b, 'p']}] + let edits += [{'func': 'rename', 'args': [filename_a, filename_b]}] + if bufloaded(buf_a.bufnr) + let edits += [{'func': 's:rename_buffer', 'args': [buf_a, filename_b]}] endif - if exists('lines_b') - let fmt = g:esearch#out#win#entry_with_sign_fmt - let i = 0 - let win_lines_b = copy(lines_b) - while i < len(win_lines_b) - let win_lines_b[i] = printf(fmt, '', i + 1, win_lines_b[i]) - let i += 1 - endw - let win_lines_b = ['', fnameescape(relative_filename_b)] + win_lines_b - - call esearch#out#win#update#add_context(self.esearch.contexts, relative_filename_b, linecount + 2, 0) + if exists('overrided_lines_b') + let win_lines_b = s:win_lines_b(relative_filename_b, overrided_lines_b) + let edits += [{'func': 's:add_context', 'args': [self.esearch, linecount, relative_filename_b, win_lines_b, overrided_lines_b]}] + let edits += [{'func': 's:add_undos', 'args': [self.esearch, self.win_undos, self.state_undos, win_lines_b]}] let linecount += len(win_lines_b) - let self.esearch.contexts[-1].lines = esearch#util#list2dict(lines_b) - let self.esearch.contexts[-1].end = linecount - let self.win_undos += [{'func': 'appendline', 'args': ['$', win_lines_b]}] - - let ids = [self.esearch.contexts[-2].id] + repeat([self.esearch.contexts[-1].id], len(win_lines_b) - 1) - let self.state_undos += [{'func': 'extend', 'args': [ids]}] - - unlet lines_b + unlet overrided_lines_b endif let ctx_a.filename = relative_filename_b endfor - let self.esearch.linecount = linecount + let edits += [{'func': 's:set_linecount', 'args': [self.esearch, linecount]}] + + + for edit in edits + call call(edit.func, edit.args) + endfor +endfu + +fu! s:win_lines_b(relative_filename_b, overrided_lines_b) abort + let fmt = g:esearch#out#win#entry_with_sign_fmt + let i = 0 + let win_lines_b = copy(a:overrided_lines_b) + while i < len(win_lines_b) + let win_lines_b[i] = printf(fmt, '', i + 1, win_lines_b[i]) + let i += 1 + endw + return ['', fnameescape(a:relative_filename_b)] + win_lines_b +endfu + +fu! s:add_undos(esearch, win_undos, state_undos, win_lines_b) abort + let ids = [a:esearch.contexts[-2].id] + repeat([a:esearch.contexts[-1].id], len(a:win_lines_b) - 1) + call extend(a:win_undos, [{'func': 'appendline', 'args': ['$', a:win_lines_b]}]) + call extend(a:state_undos, [{'func': 'extend', 'args': [ids]}]) +endfu + +fu! s:add_context(esearch, linecount, relative_filename_b, win_lines_b, overrided_lines_b) abort + let [begin, end] = [a:linecount + 2, a:linecount + len(a:win_lines_b)] + call esearch#out#win#update#add_context(a:esearch.contexts, a:relative_filename_b, begin, 0) + let a:esearch.contexts[-1].lines = esearch#util#list2dict(a:overrided_lines_b) + let a:esearch.contexts[-1].end = end +endfu + +fu! s:set_linecount(esearch, linecount) abort + let a:esearch.linecount = a:linecount +endfu + +fu! s:rename_buffer(buf_a, filename_b) abort + let hidden = esearch#let#restorable({'&l:hidden': 1}) + silent call a:buf_a.bufdo('silent! saveas! ' . fnameescape(a:filename_b) . '| bdelete! # ') + call hidden.restore() endfu fu! s:Writer.update_state(state, edits) abort diff --git a/spec/rename.vader b/spec/rename.vader index fc3d91407..a0891917e 100644 --- a/spec/rename.vader +++ b/spec/rename.vader @@ -87,21 +87,6 @@ Then: AssertEqual readfile(g:file_b), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) -Execute (Rename with overwriting a file using :write!): - let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' - exe 'bwipe' Fixture(g:file_b, 'contentb') - call esearch#init() - exe "norm /file.txt\ccelif/nested/dir.txt" -Then: - write! - Assert !filereadable(g:file_a) - AssertEqual readfile(g:file_b), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | doau TextChanged | write - " Assert filereadable(g:file_b), 'TODO can be fixed in the future' - AssertEqual readfile(g:file_a), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - Execute (Rename with overwriting a file and say (N)o): let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' exe 'bwipe' Fixture(g:file_b, 'contentb') @@ -114,22 +99,6 @@ Then: Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) AssertEqual readfile(g:file_b), ['contentb'] -Execute (Rename with overwriting a file and say (Y)es): - let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' - exe 'bwipe' Fixture(g:file_b, 'contentb') - call esearch#init() - exe "norm /file.txt\ccelif/nested/dir.txt" -Then: - let g:esearch_overwrite = 1 - write - Assert !filereadable(g:file_a) - AssertEqual readfile(g:file_b), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - undo | doau TextChanged | write - Assert !filereadable(g:file_b), "TODO can be fixed in the future" - AssertEqual readfile(g:file_a), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - Execute (Rename with overwriting a dir using :write!): let g:file_b = g:esearch.cwd.'elif/nested/dir' call mkdir(g:file_b, 'p') @@ -151,19 +120,51 @@ Then: write AssertEqual readfile(g:file_a), g:content Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" + Assert !filereadable(g:file_b) -Execute (Rename with overwriting a dir and say (Y)es): - let g:file_b = g:esearch.cwd.'elif/nested/dir' - call mkdir(g:file_b, 'p') - call esearch#init() - exe "norm /file.txt\ccelif/nested/dir" -Then: - let g:esearch_overwrite = 1 - write - AssertEqual readfile(g:file_a), g:content - Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) - Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" +" Execute (Rename with overwriting a file using :write!): +" let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' +" exe 'bwipe' Fixture(g:file_b, 'contentb') +" call esearch#init() +" exe "norm /file.txt\ccelif/nested/dir.txt" +" Then: +" write! +" Assert !filereadable(g:file_a) +" AssertEqual readfile(g:file_b), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) +" undo | doau TextChanged | write +" " Assert filereadable(g:file_b), 'TODO can be fixed in the future' +" AssertEqual readfile(g:file_a), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + +" Execute (Rename with overwriting a file and say (Y)es): +" let g:file_b = g:esearch.cwd.'elif/nested/dir.txt' +" exe 'bwipe' Fixture(g:file_b, 'contentb') +" call esearch#init() +" exe "norm /file.txt\ccelif/nested/dir.txt" +" Then: +" let g:esearch_overwrite = 1 +" write +" Assert !filereadable(g:file_a) +" AssertEqual readfile(g:file_b), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) +" undo | doau TextChanged | write +" Assert !filereadable(g:file_b), "TODO can be fixed in the future" +" AssertEqual readfile(g:file_a), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) + +" Execute (Rename with overwriting a dir and say (Y)es): +" finish " TODO +" let g:file_b = g:esearch.cwd.'elif/nested/dir' +" call mkdir(g:file_b, 'p') +" call esearch#init() +" exe "norm /file.txt\ccelif/nested/dir" +" Then: +" let g:esearch_overwrite = 1 +" write +" AssertEqual readfile(g:file_a), g:content +" Assert !bufloaded(g:file_a) && !bufloaded(g:file_b) +" Assert split(execute('messages'), "\n")[-1] =~# "can't overwite the directory" Execute (Rename deleted after the search is done): let g:file_b = g:esearch.cwd.'elif/nested/dir' From 243ec21aac013fb00ed94b72ff64ab4026ff018c Mon Sep 17 00:00:00 2001 From: eugen0329 Date: Thu, 25 Feb 2021 18:55:27 +0300 Subject: [PATCH 9/9] add defx reference --- autoload/esearch/writer.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/esearch/writer.vim b/autoload/esearch/writer.vim index ab211434a..024973561 100644 --- a/autoload/esearch/writer.vim +++ b/autoload/esearch/writer.vim @@ -163,6 +163,7 @@ fu! s:set_linecount(esearch, linecount) abort endfu fu! s:rename_buffer(buf_a, filename_b) abort + " commands are inspired by defx.nvim let hidden = esearch#let#restorable({'&l:hidden': 1}) silent call a:buf_a.bufdo('silent! saveas! ' . fnameescape(a:filename_b) . '| bdelete! # ') call hidden.restore()