markonm / traces.vim

Range, pattern and substitute preview for Vim
MIT License
735 stars 13 forks source link

Feature request: support substitution preview for tpope/vim-abolish #25

Closed bennyyip closed 3 years ago

bennyyip commented 5 years ago

Substitution preview is pretty awesome, but it would be great to have support for https://github.com/tpope/vim-abolish#substitution.

bennyyip commented 5 years ago

I took a look into trace.vim, and it seems easy to support :Subvert. Maybe we should add a way to disable :Subvert preview for users who does not use abolish before this PR get merged.

daern91 commented 3 years ago

This one would be fantastic. Any chances of seeing it integrated?

markonm commented 3 years ago

It's possible but abolish.vim would first need to expose its lhs generation. I made a quick attempt but you'll need to patch both plugins to enable the preview. Test and let me know how it works.

Patch for abolish.vim

diff --git a/plugin/abolish.vim b/plugin/abolish.vim
index 6fd332d..486d32e 100644
--- a/plugin/abolish.vim
+++ b/plugin/abolish.vim
@@ -432,6 +432,13 @@ function! Abolished()
   return get(g:abolish_last_dict,submatch(0),submatch(0))
 endfunction

+function! AbolishLhs(bad,good,flags) abort
+  let opts = s:normalize_options(a:flags)
+  let dict = s:create_dictionary(a:bad,a:good,opts)
+  let lhs = s:pattern(dict,opts.boundaries)
+  return lhs
+endfunction
+
 function! s:substitute_command(cmd,bad,good,flags)
   let opts = s:normalize_options(a:flags)
   let dict = s:create_dictionary(a:bad,a:good,opts)

Patch for traces.vim

diff --git a/autoload/traces.vim b/autoload/traces.vim
index 3f7d87a..7c05f27 100644
--- a/autoload/traces.vim
+++ b/autoload/traces.vim
@@ -10,6 +10,7 @@ let s:has_matchdelete_win = has('patch-8.1.1741')
 let s:cmd_pattern = '\v\C^%('
                 \ . 'g%[lobal][[:alnum:]]@!\!=|'
                 \ . 's%[ubstitute][[:alnum:]]@!|'
+                \ . 'Subvert[[:alnum:]]@!|'
                 \ . 'sm%[agic][[:alnum:]]@!|'
                 \ . 'sno%[magic][[:alnum:]]@!|'
                 \ . 'sor%[t][[:alnum:]]@!\!=|'
@@ -470,6 +471,22 @@ function! s:parse_substitute(cmdl) abort
   return args
 endfunction

+function! s:parse_subvert(cmdl) abort
+  call s:trim(a:cmdl.string)
+  let pattern = '\v^([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|\1@!&.)*)%((\1)%((%(\\.|\1@!&.)*)%((\1)([&cegiInp#lr]+)=)=)=)=$'
+  let args = {}
+  let r = matchlist(a:cmdl.string[0], pattern)
+  if len(r) && !empty(r[2])
+    let args.delimiter        = r[1]
+    let args.pattern_org      = r[2]
+    let args.pattern          = s:add_opt(AbolishLhs(args.pattern_org, r[4], r[6]), a:cmdl)
+    let args.string           = r[4]
+    let args.last_delimiter   = r[5]
+    let args.flags            = r[6]
+  endif
+  return args
+endfunction
+
 function! s:parse_sort(cmdl) abort
   call s:trim(a:cmdl.string)
   let pattern = '\v^.{-}([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|.){-})%((\1)|$)'
@@ -490,6 +507,8 @@ function! s:parse_command(cmdl) abort
     let a:cmdl.cmd.args = s:parse_substitute(a:cmdl)
   elseif a:cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
     let a:cmdl.cmd.args = s:parse_sort(a:cmdl)
+  elseif a:cmdl.cmd.name == 'Subvert'
+    let a:cmdl.cmd.args = s:parse_subvert(a:cmdl)
   endif
 endfunction

@@ -1129,6 +1148,8 @@ function! traces#init(cmdl, view) abort
       call s:preview_global(cmdl)
     elseif cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
       call s:preview_sort(cmdl)
+    elseif cmdl.cmd.name == 'Subvert'
+      call s:preview_substitute(cmdl)
     endif

     if empty(cmdl.cmd.name) && empty(cmdl.range.specifier)
wimstefan commented 3 years ago

It works very well with Subvert but not with it's synonym S ... Not a big thing though - I'm very happy to see Abolish operations being highlighted as well now :)

daern91 commented 3 years ago

I experience the same as @wimstefan.

Also one more small thing, the preview is wrong for title-preview CleanShot 2020-12-10 at 12 03 46

markonm commented 3 years ago

Here's a new attempt. This one does not require patching of abolish.vim and enables preview for S.

diff --git a/autoload/traces.vim b/autoload/traces.vim
index 3f7d87a..d968b67 100644
--- a/autoload/traces.vim
+++ b/autoload/traces.vim
@@ -10,6 +10,7 @@ let s:has_matchdelete_win = has('patch-8.1.1741')
 let s:cmd_pattern = '\v\C^%('
                 \ . 'g%[lobal][[:alnum:]]@!\!=|'
                 \ . 's%[ubstitute][[:alnum:]]@!|'
+                \ . '(Subvert|S)[[:alnum:]]@!|'
                 \ . 'sm%[agic][[:alnum:]]@!|'
                 \ . 'sno%[magic][[:alnum:]]@!|'
                 \ . 'sor%[t][[:alnum:]]@!\!=|'
@@ -470,6 +471,25 @@ function! s:parse_substitute(cmdl) abort
   return args
 endfunction

+function! s:parse_subvert(cmdl) abort
+  " https://stackoverflow.com/a/39216373
+  " 'dirty trick' to accesss local functions
+  let a = '<SNR>' . matchstr(matchstr(split(execute('scriptnames'), "\n"), 'abolish.vim'), '^\s*\zs\d\+') . '_'
+  call s:trim(a:cmdl.string)
+  let pattern = '\v^([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|\1@!&.)*)%((\1)%((%(\\.|\1@!&.)*)%((\1)([&cegiInp#lr]+)=)=)=)=$'
+  let args = {}
+  let r = matchlist(a:cmdl.string[0], pattern)
+  if len(r) && !empty(r[2])
+    let args.delimiter        = r[1]
+    let args.pattern_org      = r[2]
+    let args.pattern          = substitute({a}substitute_command('', r[2], r[4], r[6])[1:], '\/\\=Abolished.*', '', '')
+    let args.string           = r[4]
+    let args.last_delimiter   = r[5]
+    let args.flags            = r[6]
+  endif
+  return args
+endfunction
+
 function! s:parse_sort(cmdl) abort
   call s:trim(a:cmdl.string)
   let pattern = '\v^.{-}([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|.){-})%((\1)|$)'
@@ -490,6 +510,8 @@ function! s:parse_command(cmdl) abort
     let a:cmdl.cmd.args = s:parse_substitute(a:cmdl)
   elseif a:cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
     let a:cmdl.cmd.args = s:parse_sort(a:cmdl)
+  elseif a:cmdl.cmd.name =~# '\v^(Subvert|S)$'
+    let a:cmdl.cmd.args = s:parse_subvert(a:cmdl)
   endif
 endfunction

@@ -1129,6 +1151,8 @@ function! traces#init(cmdl, view) abort
       call s:preview_global(cmdl)
     elseif cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
       call s:preview_sort(cmdl)
+    elseif cmdl.cmd.name =~# '\v^(Subvert|S)$'
+      call s:preview_substitute(cmdl)
     endif

     if empty(cmdl.cmd.name) && empty(cmdl.range.specifier)

Obviously this is all going to stop working if abolish renames s:substitute_command :-)

@daern91 I can't fix that due to #18. It's either wrong case or no highlight. Abolish actually preserves case but it preserves it on traces.vim pattern markers...

markonm commented 3 years ago

Actuality, I managed to fix incorrect case bug. I put it on subvert branch. Could you test and see if anything else is misbehaving?

daern91 commented 3 years ago

Actuality, I managed to fix incorrect case bug. I put it on subvert branch. Could you test and see if anything else is misbehaving?

Absolutely amazing! It all works for me now. S, Subvert, and even correct preview in both visual mode and with command mode global :%S...

Huge thanks for this @markonm!

wimstefan commented 3 years ago

Wow that's really fantastic! I've always avoided abolish because highlighting was not supported - those days are over and I can enjoy all the benefits of vim-abolish together with traces.vim :-D

Thank you so much @markonm 👍👍👍

markonm commented 3 years ago

The support for :Subvert is now in the master branch. It is disabled by default. To enable add the following to your vimrc:

let g:traces_abolish_integration = 1