A Vim refactoring tool for refactoring Vim scripts. Tester wanted

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

A Vim refactoring tool for refactoring Vim scripts. Tester wanted

Klaus Horsten
Hallo,

I have written a  Vim refactoring tool for refactoring Vim scripts.

The scripts contains only one method - the most important
refactoring: extract method.

What you can do:

Example:
" A random vim code snipett
"
" PREVIOUS
"
" "
" " Insert call to new function
" "
" 'a
" exe "normal icall " . method_name ."(".parameters.")"
" normal ==
"
" Mark code lines with Vjj, call Extract Method with \em
"
"
" AFTER
"
" The original code is replaced with a call to the new build method:
"
" "
" " Insert call to new function
" "
" call InsertCallToNewFunction()
"
" The new method is put after the next "endfun(ction)" that can be
found.
" If none is found it comes immediately after the call.
"
" The new function:
"
" function! InsertCallToNewFunction()
" 'a
" exe "normal icall " . method_name ."(".parameters.")"
" normal ==
" endfunction

Before publishing the script at vim.org I want to test it.

Could someone test it please?

There seems to be a problem with indention in some environments (mine
is windows 2000, gvim 6.3, gvim 6.4).
The newly created funtion should be properly indented.

It works fine for me so I could not figure out where the problem is.

Hope you like it.

Best regards,

Klaus



" **********************************************************
" Vim Refactoring
" **********************************************************
"
" A Vim refactoring tool for refactoring Vim scripts.
"
" Refactorings:
" * Extract Method
"
" Goal for Extract Method:
" * Create reusable functions
" * Make code easier to read.
"
" Usage:
" Visually select codelines to extract.
" Type \em (<leader>em) for extract method.
"
" Example:
"
" A random vim code snipett
"
" PREVIOUS
"
" "
" " Insert call to new function
" "
" 'a
" exe "normal icall " . method_name ."(".parameters.")"
" normal ==
"
" Mark code lines with Vjj, call Extract Method with \em
"
"
" AFTER
"
" The original code is replaced with a call to the new build method:
"
" "
" " Insert call to new function
" "
" call InsertCallToNewFunction()
"
" The new method is put after the next "endfun(ction)" that can be found.
" If none is found it comes immediately after the call.
"
" The new function:
"
" function! InsertCallToNewFunction()
" 'a
" exe "normal icall " . method_name ."(".parameters.")"
" normal ==
" endfunction
"
" Navigation:
" After creation of new method:
" Jump to new caller of the function with mark a.
" Jump to new method with mark b.
"
" **********************************************************
"
" Installation:
" For Vim 6.3 and higher.
"   Source it some way e.g. drop it in the ftplugin-directory.
"
" **********************************************************
"
" Author: Klaus Horsten <horsten at gmx.at>  
"
" Credits: Staale Flock for testing
"
" Copyright: No claim. Please enhance it. Send me a mail!
"
" Warranty: No warranty of any kind. Use it on your own risk. Undo changes
" with u. Look in swap file for a previous version of your script.
"
" Version: 0.2
"
" **********************************************************

"
" Menu
"
if has("gui_running")
        vmenu Refactoring.Extract\ Method<tab><Leader>em :call VimExtractMethod()<cr>
endif

"------------------------------------------------------------------------------

"
" Mappings
"
vmap <Leader>em :call VimExtractMethod()<cr>

"------------------------------------------------------------------------------


"
" Extract Method
"
function! VimExtractMethod() range
       
        "
        " Get name of new function
        "
  let method_name = s:GetNameFromUser("Name of new method:")
        if method_name == ""
          return
        endif
        "Function name in Vim should always be upper case
        let method_name = substitute(method_name,'^[a-z]','\=toupper(submatch(0))','')

        "
        " Get codelines to extract
        "
        call s:DeleteVisualSelectedTextIntoRegisterC()
        call s:SetMark("a")

        "
        " Get new paramters
        "
        let parameters = s:GetParametersFromUser()

        "
        "Get new return value
        "
        let IsAddReturn = confirm("Add return variable? ", "Yes\nNo\nCancel", 1)
        if IsAddReturn == 1
                let return_name = s:GetNameFromUser("Name of return variable:")
        endif

        "
        " Insert call to new function
        "
        'a
        exe "normal icall " . method_name ."(".parameters.")\n"
        normal k==

        "
        " Go to end of current function
        "
        call search('^\s*endfun', 'e')
        call s:InsertBlankLinesBelow(2)

        "
        " Create new function
        "
        "Begin
        exe "normal ifunction! " . method_name ."(".parameters.")"
        call s:SetMark("b")
        call s:InsertBlankLinesBelow(2)
        call s:SetMark("d")
        "Body
        normal k
        call s:PasteTextFromRegisterC()
        exe "normal V'd==\<esc>"
        'd
        "Return
        if IsAddReturn == 1
                exe "normal ireturn ".return_name ."\n"
                normal k==j
        endif
        "End
        exe "normal iendfunction\n"
        normal ==

        "Clean up
        call s:RemoveMark("d")
        'b
        normal j
        if s:IsEmptyLine() == 1
                d
        endif
        'b
endfunction

"------------------------------------------------------------------------------
"
" Reusable functions - usable also for several other (future) functions
" (code reuse)
"
"------------------------------------------------------------------------------

"
" Check if line is empty
"
function! s:IsEmptyLine()
        if getline(".") !~ '\w'
                return 1
        endif
        return -1
endfunction

"------------------------------------------------------------------------------

"
" Get parameters from user
"
function! s:GetParametersFromUser()
        let IsAdd = confirm("Add paramter? ", "Yes\nNo\nCancel", 1)
        let g:parameters = ""
        if IsAdd == 1
                let g:parameters = s:GetNameFromUser("Parameter name:")
                echo "Paramters: " .g:parameters
                call s:AddAnotherParamter()
        endif
        return g:parameters
endfunction

function! s:AddAnotherParamter()
  let IsAnotherParameter = confirm("Another paramter? ", "Yes\nNo\nCancel", 1)
                if IsAnotherParameter == 1
                        call s:AddParameter()
                        echo "Paramters: " .g:parameters
                else
                        return
                endif
                call s:AddAnotherParamter()
endfunction

function! s:AddParameter()
        echo "Paramters: " .g:parameters
        let parameter = s:GetNameFromUser("Parameter name:")
        if parameter != ""
                let g:parameters = g:parameters . ", ". parameter
        endif
        return g:parameters
endfunction

"------------------------------------------------------------------------------

"
" Get names for code entities from user
"
function! s:GetNameFromUser(text)
  let name = inputdialog(a:text." ")
        return name
endfunction

"------------------------------------------------------------------------------

"
" Move text
"
function! s:DeleteVisualSelectedTextIntoRegisterC() range
 exe "normal \<esc>"
 normal gv"cd
 exe "normal \<esc>"
endfunction

function! s:PasteTextFromRegisterC()
        normal "cp
endfunction


"------------------------------------------------------------------------------

"
" Marks
"
function! s:SetMark(mark)
        exe "normal m" . a:mark
endfunction

function! s:RemoveMark(mark)
        try
                exe "normal '" . a:mark
                normal \mh
        catch
        endtry
endfunction

"------------------------------------------------------------------------------

"
" Blank lines
"

" Blank lines above
function! InsertBlankLinesAbove(number)
        let number = a:number
        while number > 0
                call s:InsertOneBlankLineAbove()
          let number = number - 1
        endwhile
endfunction

function! s:InsertOneBlankLineAbove()
        normal O
endfunction

" Blank lines below
function! s:InsertBlankLinesBelow(number)
        let number = a:number
        while number > 0
                call s:InsertOneBlankLineBelow()
          let number = number - 1
        endwhile
endfunction

function! s:InsertOneBlankLineBelow()
        normal o
endfunction

"------------------------------------------------------------------------------
Reply | Threaded
Open this post in threaded view
|

Re: A Vim refactoring tool for refactoring Vim scripts. Tester wanted

Luc Hermitte
Klaus Horsten <[hidden email]> wrote:

> I have written a  Vim refactoring tool for refactoring Vim scripts.
>
> The scripts contains only one method - the most important
> refactoring: extract method.
>

Hello Klaus.

I have just given a quick look into your vim-ftplugin.
It brokes in VimExtractMethod line 41, with the error message "invalid arguement
e"
The context is vim 7.0aa on Windows XP pro (the one compiled by Tony), plus many
scripts of mine. In particular, my bracketing system that maps "(", and other
similar things, which had side effects with your ftplugin. Do not bother with
making your ftplugin compatible with my plugin, it can become quite complex as
there are several alternative bracketing systems, and even much more mappings
to '(' ; the easier thing to do is to use :put, or normal!

Here are a few suggestions for your plugin:
- never use :normal, but always :normal!, unless you want mappings to expand
- prefer :put= or <c-r>= + :return over exe "normal! ifoo\<esc>"
- Propose remappable mappings (with for instance <plug>, instead of hardcoded
ones -- I use \em in several langages to emphase the selection (italics)).
- As your plugin is more like an ftplugin, make the mapping local to the current
buffer.
- It may be preferable to not insert right away the code of the new function.


In case it may help, I already started to work on a similar, but yet different
refactoring plugin. As usual, it is tunable, and thus quite complex. At this
time, it is still in an early beta stage. You can found it at:
  <http://hermitte.free.fr/vim/ressources/ lh-refactor.tar.gz>

HTH,

--
Luc Hermitte
http://hermitte.free.fr/vim/
Reply | Threaded
Open this post in threaded view
|

Re: A Vim refactoring tool for refactoring Vim scripts. Tester wanted

Klaus Horsten
Hi Luc,

thank you very much for your feedback

> I have just given a quick look into your vim-ftplugin.
> It brokes in VimExtractMethod line 41, with the error message "invalid arguement
> e"

Here I have

call search('^\s*endfun', 'e')

e means: ignore errors - if there is no endfun ignore it.

Isn't this a true option? Is it possible to use e here? It seems not -
 or is it?

Alternatively I could use a try-catch without e.


> The context is vim 7.0aa on Windows XP pro (the one compiled by Tony), plus many
> scripts of mine. In particular, my bracketing system that maps "(", and other
> similar things, which had side effects with your ftplugin. Do not bother with
> making your ftplugin compatible with my plugin, it can become quite complex as
> there are several alternative bracketing systems, and even much more mappings
> to '(' ; the easier thing to do is to use :put, or normal!
>
> Here are a few suggestions for your plugin:
> - never use :normal, but always :normal!, unless you want mappings to expand

OK

> - prefer :put= or <c-r>= + :return over exe "normal! ifoo\<esc>"

OK - but why? What is the advantage?

> - Propose remappable mappings (with for instance <plug>, instead of hardcoded
> ones -- I use \em in several langages to emphase the selection (italics)).

I have <leader>em - although I write in the example \em.

> - As your plugin is more like an ftplugin, make the mapping local to the current
> buffer.
Originally it is a ftplugin - I have forgot to say this.

> - It may be preferable to not insert right away the code of the new function.

I could write the new function in a register and prompt "New function
is in register r. Paste it at the destination place with "rp". Would
this be better?

>
>
> In case it may help, I already started to work on a similar, but yet different
> refactoring plugin. As usual, it is tunable, and thus quite complex. At this
> time, it is still in an early beta stage. You can found it at:
>   <http://hermitte.free.fr/vim/ressources/ lh-refactor.tar.gz>

Thank you. I have downloaded it. I will have a closer look later.

In fact, my refactoring tool is only a small segment of a big one.

I have very big one for C# (also for PHP - which is quite similar).
With many refactorings. But it is not possible to publish it. It is
too big and there are too many things that could not work in other
environments. And I am constantly refactoring it itself.

I first want to publish this small script and see what I should make
different.

Thank you very much for yur help.

I hope I get some answers to my questions.

Best regards,

Klaus




>
> HTH,
>
> --
> Luc Hermitte
> http://hermitte.free.fr/vim/
>


Reply | Threaded
Open this post in threaded view
|

Re: A Vim refactoring tool for refactoring Vim scripts. Tester wanted

Luc Hermitte
Klaus Horsten <[hidden email]> wrote:

> Here I have
>    call search('^\s*endfun', 'e')
> e means: ignore errors - if there is no endfun ignore it.
>
> Isn't this a true option? Is it possible to use e here? It seems not -
>  or is it?

I am not aware of this option. Anyway, you do not need it. search() never yells
on errors. Instead it returns 0 when the pattern is not found. searchpair() is
to be considered here as you may not want to find the "endf\%[unc]" from
another function, or within the context of a comment or a string.

> > - prefer :put= or <c-r>= + :return over exe "normal! ifoo\<esc>"
> OK - but why? What is the advantage?

Sorry. Actually it is my preferences. It is my general feeling that I did have
less bugs and things that I did not want to expand but still do with this
approach, than with the other ones. :put seems a little bit quicker also -- my
senses may have tricked me.

> > - It may be preferable to not insert right away the code of the new
> > function.
>
> I could write the new function in a register and prompt "New function
> is in register r. Paste it at the destination place with "rp". Would
> this be better?

Personnally, I chose to delay the insertion until :PutExtractedFunction[!], and
to not mess with registers. It is quite limited as only one function can be
memorized. However as I do not think that memorizing several extracted
functions is a good thing, it is closer to a feature than to a restriction.

> In fact, my refactoring tool is only a small segment of a big one.
>
> I have very big one for C# (also for PHP - which is quite similar)

I remember your tip about C#. Good inspiration.

> I first want to publish this small script and see what I should make
> different.

Good luck! I will definitivelly have a look into it.

> Thank you very much for yur help.

You are welcome.


--
Luc Hermitte