Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Added Live Git Annotations (screenshots inside) #50

Merged
merged 4 commits into from

7 participants

@cj
cj commented

I found a slight bug, when using comments on the same line as code, it thinks everything is a comment including the line below it:

@cj
cj commented

It also appears that in ruby it thinks everything is a comment when it's not.

@buhrmi

I'm not sure I understand you correctly. The purpose of the feature is to annotate changes in the buffer compared to the latest version in the branch. The blue lines indicate changed lines. Not comments.

@cj
cj commented

I thought green indicates code lines added, blue is comment lines added and red is code lines removed?

@buhrmi

Ah. No.
green = added line
blue = changed line
red line = removed code

@cj
cj commented

ahhh... it all makes sense now :D thank you.

Did you see my other comment on is it possible to show the code in red that got deleted instead of just a red line?

@ericclemmons

Does this PR need to be merged down? The README indicates this command is available, though it's not (yet)

@mgcrea

Please merge this!

@sirkitree

Yeah, this should be removed from the Wiki if it's not been merged yet.

@sheldon
Collaborator

I'm considering requesting that your fork be taken as the auto install version, as this pull request hasn't been merged. what do you think?

@buhrmi

No. I was not able to test it on windows. And I believe it won't work since it's using the diff command through the IO pipes which isn't available on Windows (I think).

@sheldon
Collaborator
@sheldon
Collaborator

I've been trying to get this patch working together with another pull request which modifies the CommandThread constructor in a different way. I'm having some problems, would appreciate some help.

I seem to have the initial annotations working fine, but the live ones are breaking. What I have working is on https://github.com/sheldon/sublime-text-2-git/tree/annotations if you wouldn't mind taking a look.

@sheldon
Collaborator

for anyone that's interested, I've got this successfully merged with the pull request to add git show (#30). both are merged along with most of the outstanding pull requests on my master branch if you'd like to get them added to this original one @kemayo : https://github.com/sheldon/sublime-text-2-git

@sheldon sheldon merged commit c12f445 into from
@meshy

This clobbers text colouring :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 7, 2011
  1. @buhrmi

    Added Annotations

    buhrmi authored
  2. @buhrmi
Commits on Nov 8, 2011
  1. @buhrmi
  2. @buhrmi

    forgot a debug print

    buhrmi authored
This page is out of date. Refresh to see the latest.
Showing with 130 additions and 5 deletions.
  1. +8 −0 Default.sublime-commands
  2. +2 −0  Main.sublime-menu
  3. +120 −5 git.py
View
8 Default.sublime-commands
@@ -72,6 +72,14 @@
"command": "git_push"
}
,{
+ "caption": "Git: Annotate",
+ "command": "git_annotate"
+ }
+ ,{
+ "caption": "Git: Clear Annotation",
+ "command": "git_clear_annotation"
+ }
+ ,{
"caption": "Git: Custom Command",
"command": "git_custom"
}
View
2  Main.sublime-menu
@@ -18,6 +18,8 @@
,{ "caption": "Blame", "command": "git_blame" }
,{ "caption": "Checkout", "command": "git_checkout" }
,{ "caption": "Graph", "command": "git_graph" }
+ ,{ "caption": "Annotate", "command": "git_annotate" }
+ ,{ "caption": "Clear Annotation", "command": "git_clear_annotation" }
]
}
,{
View
125 git.py
@@ -5,6 +5,8 @@
import subprocess
import functools
import tempfile
+import os.path
+import re
# when sublime loads a plugin it's cd'd into the plugin directory. Thus
# __file__ is useless for my purposes. What I want is "Packages/Git", but
@@ -57,11 +59,13 @@ def _make_text_safeish(text, fallback_encoding):
class CommandThread(threading.Thread):
- def __init__(self, command, on_done, working_dir="", fallback_encoding=""):
+ def __init__(self, command, on_done, working_dir="", fallback_encoding="", stdin="", stdout=subprocess.PIPE):
threading.Thread.__init__(self)
self.command = command
self.on_done = on_done
self.working_dir = working_dir
+ self.stdin = stdin
+ self.stdout = stdout
self.fallback_encoding = fallback_encoding
def run(self):
@@ -73,9 +77,12 @@ def run(self):
os.chdir(self.working_dir)
proc = subprocess.Popen(self.command,
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ stdout=self.stdout, stderr=subprocess.STDOUT,
+ stdin=subprocess.PIPE,
shell=shell, universal_newlines=True)
- output = proc.communicate()[0]
+ output = proc.communicate(self.stdin)[0]
+ if not output:
+ output = ''
# if sublime's python gets bumped to 2.7 we can just do:
# output = subprocess.check_output(self.command)
main_thread(self.on_done,
@@ -92,7 +99,7 @@ def run(self):
# A base for all commands
class GitCommand:
def run_command(self, command, callback=None, show_status=True,
- filter_empty_args=True, **kwargs):
+ filter_empty_args=True, no_save=False, **kwargs):
if filter_empty_args:
command = [arg for arg in command if arg]
if 'working_dir' not in kwargs:
@@ -101,7 +108,7 @@ def run_command(self, command, callback=None, show_status=True,
kwargs['fallback_encoding'] = self.active_view().settings().get('fallback_encoding').rpartition('(')[2].rpartition(')')[0]
s = sublime.load_settings("Git.sublime-settings")
- if s.get('save_first') and self.active_view() and self.active_view().is_dirty():
+ if s.get('save_first') and self.active_view() and self.active_view().is_dirty() and not no_save:
self.active_view().run_command('save')
if command[0] == 'git' and s.get('git_command'):
command[0] = s.get('git_command')
@@ -557,3 +564,111 @@ def on_input(self, command):
command_splitted = ['git'] + shlex.split(command)
print command_splitted
self.run_command(command_splitted)
+
+
+class GitClearAnnotationCommand(GitTextCommand):
+ def run(self, view):
+ self.active_view().settings().set('live_git_annotations', False)
+ self.view.erase_regions('git.changes.x')
+ self.view.erase_regions('git.changes.+')
+ self.view.erase_regions('git.changes.-')
+
+
+class GitAnnotationListener(sublime_plugin.EventListener):
+ def on_modified(self, view):
+ if not view.settings().get('live_git_annotations'):
+ return
+ view.run_command('git_annotate')
+
+
+class GitAnnotateCommand(GitTextCommand):
+ # Unfortunately, git diff does not support text from stdin, making a *live* annotation
+ # difficult. Therefore I had to resort to the system diff command. (Problems on win?)
+ # This works as follows:
+ # 1. When the command is run for the first time for this file, a temporary file with the
+ # current state of the HEAD is being pulled from git.
+ # 2. All consecutive runs will pass the current buffer into diffs stdin. The resulting
+ # output is then parsed and regions are set accordingly.
+ def run(self, view):
+ # If the annotations are already running, we dont have to create a new tmpfile
+ if self.active_view().settings().get('live_git_annotations'):
+ self.compare_tmp(None)
+ return
+ self.tmp = tempfile.NamedTemporaryFile()
+ self.active_view().settings().set('live_git_annotations', True)
+ root = git_root(self.get_working_dir())
+ repo_file = os.path.relpath(self.view.file_name(), root)
+ self.run_command(['git', 'show', 'HEAD:{0}'.format(repo_file)], show_status=False, no_save=True, callback=self.compare_tmp, stdout=self.tmp)
+
+ def compare_tmp(self, result):
+ all_text = self.view.substr(sublime.Region(0, self.view.size()))
+ self.run_command(['diff', '-u', self.tmp.name, '-'], stdin=all_text, no_save=True, show_status=False, callback=self.parse_diff)
+
+ # This is where the magic happens. At the moment, only one chunk format is supported. While
+ # the unified diff format theoritaclly supports more, I don't think git diff creates them.
+ def parse_diff(self, result):
+ lines = result.splitlines()
+ matcher = re.compile('^@@ -([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@')
+ diff = []
+ for line_index in range(0, len(lines)):
+ line = lines[line_index]
+ if not line.startswith('@'):
+ continue
+ match = matcher.match(line)
+ if not match:
+ continue
+ line_before, len_before, line_after, len_after = [int(match.group(x)) for x in [1, 2, 3, 4]]
+ chunk_index = line_index + 1
+ tracked_line_index = line_after - 1
+ deletion = False
+ insertion = False
+ while True:
+ line = lines[chunk_index]
+ if line.startswith('@'):
+ break
+ elif line.startswith('-'):
+ if not line.strip() == '-':
+ deletion = True
+ tracked_line_index -= 1
+ elif line.startswith('+'):
+ if deletion and not line.strip() == '+':
+ diff.append(['x', tracked_line_index])
+ insertion = True
+ elif not deletion:
+ insertion = True
+ diff.append(['+', tracked_line_index])
+ else:
+ if not insertion and deletion:
+ diff.append(['-', tracked_line_index])
+ insertion = deletion = False
+ tracked_line_index += 1
+ chunk_index += 1
+ if chunk_index >= len(lines):
+ break
+
+ self.annotate(diff)
+
+ # Once we got all lines with their specific change types (either x, +, or - for
+ # modified, added, or removed) we can create our regions and do the actual annotation.
+ def annotate(self, diff):
+ self.view.erase_regions('git.changes.x')
+ self.view.erase_regions('git.changes.+')
+ self.view.erase_regions('git.changes.-')
+ typed_diff = {'x': [], '+': [], '-': []}
+ for change_type, line in diff:
+ if change_type == '-':
+ full_region = self.view.full_line(self.view.text_point(line-1, 0))
+ position = full_region.begin()
+ for i in xrange(full_region.size()):
+ typed_diff[change_type].append(sublime.Region(position + i))
+ else:
+ point = self.view.text_point(line, 0)
+ region = self.view.full_line(point)
+ if change_type == '-':
+ region = sublime.Region(point, point + 5)
+ typed_diff[change_type].append(region)
+
+ for change in ['x', '+']:
+ self.view.add_regions("git.changes.{0}".format(change), typed_diff[change], 'git.changes.{0}'.format(change), 'dot')
+
+ self.view.add_regions("git.changes.-", typed_diff['-'], 'git.changes.-', 'dot', sublime.DRAW_EMPTY_AS_OVERWRITE)
Something went wrong with that request. Please try again.