From 20ae6f8ea0b7dbb5e1ea89bdc024b1bd6841dd45 Mon Sep 17 00:00:00 2001 From: Hugues Hiegel Date: Thu, 18 Dec 2014 12:31:03 +0100 Subject: [plugins] added Linediff --- autoload/linediff/differ.vim | 169 +++++++++++++++++++++++++++++++++++++++++++ autoload/linediff/util.vim | 4 + doc/linediff.txt | 124 +++++++++++++++++++++++++++++++ plugin/linediff.vim | 48 ++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 autoload/linediff/differ.vim create mode 100644 autoload/linediff/util.vim create mode 100644 doc/linediff.txt create mode 100644 plugin/linediff.vim diff --git a/autoload/linediff/differ.vim b/autoload/linediff/differ.vim new file mode 100644 index 0000000..7eb5c9d --- /dev/null +++ b/autoload/linediff/differ.vim @@ -0,0 +1,169 @@ +" Constructs a Differ object that is still unbound. To initialize the object +" with data, `Init(from, to)` needs to be invoked on that object. +function! linediff#differ#New(sign_name, sign_number) + let differ = { + \ 'original_buffer': -1, + \ 'diff_buffer': -1, + \ 'filetype': '', + \ 'from': -1, + \ 'to': -1, + \ 'sign_name': a:sign_name, + \ 'sign_number': a:sign_number, + \ 'sign_text': a:sign_number.'-', + \ 'is_blank': 1, + \ 'other_differ': {}, + \ + \ 'Init': function('linediff#differ#Init'), + \ 'IsBlank': function('linediff#differ#IsBlank'), + \ 'Reset': function('linediff#differ#Reset'), + \ 'Lines': function('linediff#differ#Lines'), + \ 'CreateDiffBuffer': function('linediff#differ#CreateDiffBuffer'), + \ 'SetupDiffBuffer': function('linediff#differ#SetupDiffBuffer'), + \ 'CloseDiffBuffer': function('linediff#differ#CloseDiffBuffer'), + \ 'UpdateOriginalBuffer': function('linediff#differ#UpdateOriginalBuffer'), + \ 'PossiblyUpdateOtherDiffer': function('linediff#differ#PossiblyUpdateOtherDiffer'), + \ 'SetupSigns': function('linediff#differ#SetupSigns') + \ } + + exe "sign define ".differ.sign_name." text=".differ.sign_text." texthl=Search" + + return differ +endfunction + +" Sets up the Differ with data from the argument list and from the current +" file. +function! linediff#differ#Init(from, to) dict + let self.original_buffer = bufnr('%') + let self.filetype = &filetype + let self.from = a:from + let self.to = a:to + + call self.SetupSigns() + + let self.is_blank = 0 +endfunction + +" Returns true if the differ is blank, which means not initialized with data. +function! linediff#differ#IsBlank() dict + return self.is_blank +endfunction + +" Resets the differ to the blank state. Invoke `Init(from, to)` on it later to +" make it usable again. +function! linediff#differ#Reset() dict + call self.CloseDiffBuffer() + + let self.original_buffer = -1 + let self.diff_buffer = -1 + let self.filetype = '' + let self.from = -1 + let self.to = -1 + let self.other_differ = {} + + exe "sign unplace ".self.sign_number."1" + exe "sign unplace ".self.sign_number."2" + + let self.is_blank = 1 +endfunction + +" Extracts the relevant lines from the original buffer and returns them as a +" list. +function! linediff#differ#Lines() dict + return getbufline(self.original_buffer, self.from, self.to) +endfunction + +" Creates the buffer used for the diffing and connects it to this differ +" object. +function! linediff#differ#CreateDiffBuffer(edit_command) dict + let lines = self.Lines() + let temp_file = tempname() + + exe a:edit_command . " " . temp_file + call append(0, lines) + normal! Gdd + set nomodified + + let self.diff_buffer = bufnr('%') + call self.SetupDiffBuffer() + + diffthis +endfunction + +" Sets up the temporary buffer's filetype and statusline. +" +" Attempts to leave the current statusline as it is, and simply add the +" relevant information in the place of the current filename. If that fails, +" replaces the whole statusline. +function! linediff#differ#SetupDiffBuffer() dict + let b:differ = self + + let statusline = printf('[%s:%%{b:differ.from}-%%{b:differ.to}]', bufname(self.original_buffer)) + if &statusline =~ '%[fF]' + let statusline = substitute(&statusline, '%[fF]', statusline, '') + endif + exe "setlocal statusline=" . escape(statusline, ' |') + exe "set filetype=" . self.filetype + setlocal bufhidden=hide + + autocmd BufWrite silent call b:differ.UpdateOriginalBuffer() +endfunction + +function! linediff#differ#CloseDiffBuffer() dict + if bufexists(self.diff_buffer) + exe "bdelete ".self.diff_buffer + endif +endfunction + +function! linediff#differ#SetupSigns() dict + exe "sign unplace ".self.sign_number."1" + exe "sign unplace ".self.sign_number."2" + + exe printf("sign place %d1 name=%s line=%d buffer=%d", self.sign_number, self.sign_name, self.from, self.original_buffer) + exe printf("sign place %d2 name=%s line=%d buffer=%d", self.sign_number, self.sign_name, self.to, self.original_buffer) +endfunction + +" Updates the original buffer after saving the temporary one. It might also +" update the other differ's data, provided a few conditions are met. See +" linediff#differ#PossiblyUpdateOtherDiffer() for details. +function! linediff#differ#UpdateOriginalBuffer() dict + let new_lines = getbufline('%', 0, '$') + + " Switch to the original buffer, delete the relevant lines, add the new + " ones, switch back to the diff buffer. + call linediff#util#SwitchBuffer(self.original_buffer) + let saved_cursor = getpos('.') + call cursor(self.from, 1) + exe "normal! ".(self.to - self.from + 1)."dd" + call append(self.from - 1, new_lines) + call setpos('.', saved_cursor) + call linediff#util#SwitchBuffer(self.diff_buffer) + + " Keep the difference in lines to know how to update the other differ if + " necessary. + let line_count = self.to - self.from + 1 + let new_line_count = len(new_lines) + + let self.to = self.from + len(new_lines) - 1 + call self.SetupDiffBuffer() + call self.SetupSigns() + + call self.PossiblyUpdateOtherDiffer(new_line_count - line_count) +endfunction + +" If the other differ originates from the same buffer and it's located below +" this one, we need to update its starting and ending lines, since any change +" would result in a line shift. +" +" a:delta is the change in the number of lines. +function! linediff#differ#PossiblyUpdateOtherDiffer(delta) dict + let other = self.other_differ + + if self.original_buffer == other.original_buffer + \ && self.to <= other.from + \ && a:delta != 0 + let other.from = other.from + a:delta + let other.to = other.to + a:delta + + call other.SetupSigns() + endif +endfunction diff --git a/autoload/linediff/util.vim b/autoload/linediff/util.vim new file mode 100644 index 0000000..4655b92 --- /dev/null +++ b/autoload/linediff/util.vim @@ -0,0 +1,4 @@ +" Helper method to change to a certain buffer. +function! linediff#util#SwitchBuffer(bufno) + exe "buffer ".a:bufno +endfunction diff --git a/doc/linediff.txt b/doc/linediff.txt new file mode 100644 index 0000000..e6e21ed --- /dev/null +++ b/doc/linediff.txt @@ -0,0 +1,124 @@ +============================================================================== +CONTENTS *linediff* *linediff-contents* + + Installation...........................: |linediff-installation| + Usage..................................: |linediff-usage| + Commands...............................: |linediff-commands| + Internals..............................: |linediff-internals| + Issues.................................: |linediff-issues| + + +============================================================================== +INSTALLATION *linediff-installation* + +There are several ways to install the plugin. The recommended one is by using +Tim Pope's pathogen (http://www.vim.org/scripts/script.php?script_id=2332). In +that case, you can clone the plugin's git repository like so: +> + git clone git://github.com/AndrewRadev/linediff.vim.git ~/.vim/bundle/linediff +< +If your vim configuration is under git version control, you could also set up +the repository as a submodule, which would allow you to update more easily. +The command is (provided you're in ~/.vim): +> + git submodule add git://github.com/AndrewRadev/linediff.vim.git bundle/linediff +< + +Another way is to simply copy all the essential directories inside the ~.vim/ +directory: plugin, autoload, doc. + +============================================================================== +USAGE *linediff-usage* + +The plugin provides a simple command, |:Linediff|, which is used to diff two +separate blocks of text. + +A simple example: + + def one + two + end + + def two + three + end + +If we mark the first three lines, starting from "def one", in visual mode, and +execute the |:Linediff| command, the signs "1-" will be placed at the start +and at the end of the visual mode's range. Doing the same thing on the bottom +half of the code, starting from "def two", will result in the signs "2-" +placed there. After that, a new tab will be opened with the two blocks of code +in vertical splits, diffed against each other. + +The two buffers are temporary, but when any one of them is saved, its original +buffer is updated. Note that this doesn't save the original buffer, just +performs the change. Saving is something you should do later. + +Executing the command |:LinediffReset| will delete the temporary buffers and +remove the signs. + +Executing a new |:Linediff| will do the same as |:LinediffReset|, but will +also initiate a new diff process. + +The statuslines of the two temporary buffers will be changed to contain: + - The original buffer + - The starting line of the selected segment + - The ending line of the selected segment + +If you're using a custom statusline and it contains "%f" (the current file's +name), that token will simply be substituted by the above data. Otherwise, the +entire statusline will be set to a custom one. + +============================================================================== +COMMANDS *linediff-commands* + + *:Linediff* +:Linediff The main interface of the plugin. Needs to be executed on a + range of lines, which will be marked with a sign. On the + selection of the second such range, the command will open a + tab with the two ranges in vertically split windows and + perform a diff on them. Saving one of the two buffers will + automatically update the original buffer the text was taken + from. + + When executed for a third time, a new line diff is + initiated, and the current process is reset, much like the + effect of |LinediffReset| would be. + + + *:LinediffReset* +:LinediffReset Removes the signs denoting the diffed regions and deletes + the temporary buffers, used for the diff. The original + buffers are untouched by this, which means that any updates + to them, performed by the diff process will remain. + +============================================================================== +INTERNALS *linediff-internals* + +When a block of text is diffed with the plugin, a "Differ" object is +initialized with its relevant data. The differ contains information about the +buffer number, filetype, start and end lines of the text, and a few other +things. Almost all functions the plugin uses are scoped to this object in +order to keep the interface simple. They're located under +"autoload/linediff/differ.vim" and should be fairly understandable. + +Functions that are general-purpose utilities are placed in +"autoload/linediff/util.vim". + +The two differ objects that are required for the two diff buffers are linked +to each other out of necessity. If they originate from a single buffer, +updating one would move the lines of the other, so that one would have to be +updated as well. Apart from that, they have no interaction. + +============================================================================== +ISSUES *linediff-issues* + +You shouldn't linediff two pieces of text that overlap. Not that anything +horribly bad will happen, it just won't work as you'd hope to. I don't feel +like it's a very important use case, but if someone requests sensible +behaviour in that case, I should be able to get it working. + +To report any issues or offer suggestions, use the bugtracker of the github +project at http://github.com/AndrewRadev/linediff.vim/issues + +vim:tw=78:sw=4:ft=help:norl: diff --git a/plugin/linediff.vim b/plugin/linediff.vim new file mode 100644 index 0000000..f154d6d --- /dev/null +++ b/plugin/linediff.vim @@ -0,0 +1,48 @@ +if exists("g:loaded_linediff") || &cp + finish +endif + +let g:loaded_linediff = '0.1.1' " version number +let s:keepcpo = &cpo +set cpo&vim + +" Initialized lazily to avoid executing the autoload file before it's really +" needed. +function! s:Init() + if !exists('s:differ_one') + let s:differ_one = linediff#differ#New('linediff_one', 1) + let s:differ_two = linediff#differ#New('linediff_two', 2) + endif +endfunction + +command! -range Linediff call s:Linediff(, ) +function! s:Linediff(from, to) + call s:Init() + + if s:differ_one.IsBlank() + call s:differ_one.Init(a:from, a:to) + elseif s:differ_two.IsBlank() + call s:differ_two.Init(a:from, a:to) + + call s:PerformDiff(s:differ_one, s:differ_two) + else + call s:differ_one.Reset() + call s:differ_two.Reset() + + call s:Linediff(a:from, a:to) + endif +endfunction + +command! LinediffReset call s:LinediffReset() +function! s:LinediffReset() + call s:differ_one.Reset() + call s:differ_two.Reset() +endfunction + +function! s:PerformDiff(one, two) + call a:one.CreateDiffBuffer("tabedit") + call a:two.CreateDiffBuffer("rightbelow vsplit") + + let a:one.other_differ = a:two + let a:two.other_differ = a:one +endfunction -- cgit v1.2.3