summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugues Hiegel <hugues.hiegel@qosmos.com>2014-12-18 12:31:03 +0100
committerHugues Hiegel <hugues.hiegel@qosmos.com>2014-12-18 12:31:03 +0100
commit20ae6f8ea0b7dbb5e1ea89bdc024b1bd6841dd45 (patch)
tree942923e09e5cfe1af356bda7c40f5a4c4376fc4a
parentdf0075bccc56d2f16d2de64a7063f42a7c13418f (diff)
[plugins] added Linediff
-rw-r--r--autoload/linediff/differ.vim169
-rw-r--r--autoload/linediff/util.vim4
-rw-r--r--doc/linediff.txt124
-rw-r--r--plugin/linediff.vim48
4 files changed, 345 insertions, 0 deletions
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 <buffer> 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(<line1>, <line2>)
+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