diff --git a/.gitignore b/.gitignore index 67bb3d1d..65c111c0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# Special file for testing ignored files +testfile-for-gitignore diff --git a/gutter_handlers.py b/gutter_handlers.py index 2a6fdb2b..f3bfd47c 100644 --- a/gutter_handlers.py +++ b/gutter_handlers.py @@ -52,6 +52,18 @@ def _get_view_encoding(self): encoding = encoding.replace(' ', '') return encoding + @staticmethod + def _to_unix(contents): + contents = contents.replace(b'\r\n', b'\n') + contents = contents.replace(b'\r', b'\n') + return contents + + @staticmethod + def _write(name, contents): + f = open(name, 'wb') + f.write(contents) + f.close() + def update_buf_file(self): chars = self.view.size() region = sublime.Region(0, chars) @@ -63,10 +75,8 @@ def update_buf_file(self): # Fallback to utf8-encoding contents = self.view.substr(region).encode('utf-8') - contents = contents.replace(b'\r\n', b'\n') - f = open(self.buf_temp_file.name, 'wb') - f.write(contents) - f.close() + contents = VcsGutterHandler._to_unix(contents) + VcsGutterHandler._write(self.buf_temp_file.name, contents) def total_lines(self): chars = self.view.size() @@ -81,15 +91,23 @@ def update_vcs_file(self): # between updates for performance if ViewCollection.vcs_time(self.view) > 5: open(self.vcs_temp_file.name, 'w').close() + status_args = self.get_status_args() + status = None + try: + contents, errors = self.run_command(status_args) + status = self.process_status_line(contents) + ViewCollection.update_vcs_status(self.view, status) + except Exception as e: + # print e + pass + args = self.get_diff_args() try: - contents = self.run_command(args) - contents = contents.replace(b'\r\n', b'\n') - contents = contents.replace(b'\r', b'\n') - f = open(self.vcs_temp_file.name, 'wb') - f.write(contents) - f.close() - ViewCollection.update_vcs_time(self.view) + if status == 'M': + contents, errors = self.run_command(args) + contents = VcsGutterHandler._to_unix(contents) + VcsGutterHandler._write(self.vcs_temp_file.name, contents) + ViewCollection.update_vcs_time(self.view) except Exception as e: print ("Unable to write file for diff ", e) @@ -123,9 +141,16 @@ def process_diff(self, diff_str): # this means this file is either: # - New and not being tracked *yet* # - Or it is a *gitignored* file - return ([], [], []) + status = ViewCollection.vcs_status(self.view) + if status == 'U': + # use special region 'unknown' + return ([], [], [], inserted, []) + elif status == 'I': + # use special region 'ignored' + return ([], [], [], [], inserted) + return (inserted, modified, deleted, [], []) else: - return (inserted, modified, deleted) + return (inserted, modified, deleted, [], []) def diff(self): if self.on_disk() and self.vcs_path: @@ -136,19 +161,20 @@ def diff(self): self.vcs_temp_file.name, self.buf_temp_file.name, ] - results = self.run_command(args) + results, errors = self.run_command(args) + # print errors return self.process_diff(results) else: - return ([], [], []) + return ([], [], [], []) def run_command(self, args): startupinfo = None if os.name == 'nt': startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - proc = subprocess.Popen(args, stdout=subprocess.PIPE, + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) - return proc.stdout.read() + return proc.stdout.read(), proc.stderr.read() class GitGutterHandler(VcsGutterHandler): @@ -165,6 +191,28 @@ def get_diff_args(self): ] return args + def get_status_args(self): + args = [ + self.exc_path, + '--git-dir=' + self.vcs_dir, + '--work-tree=' + self.vcs_tree, + 'status', + '--ignored', + '--porcelain', + self.vcs_path, + ] + return args + + def process_status_line(self, st): + st = st[0:2] + if st == '!!': + return 'I' + elif st == '??': + return 'U' + else: + return 'M' + # all other states we may consider as modified + class HgGutterHandler(VcsGutterHandler): def get_vcs_helper(self): @@ -180,6 +228,32 @@ def get_diff_args(self): ] return args + def get_status_args(self): + args = [ + self.exc_path, + '--repository', + self.vcs_tree, + 'status', + '--added', + '--modified', + '--unknown', + '--ignored', + '--color', + 'never', + os.path.join(self.vcs_tree, self.vcs_path), + ] + return args + + def process_status_line(self, st): + st = st[0:1] + if st == 'I': + return 'I' + elif st == '?': + return 'U' + else: + return 'M' + # all other states we may consider as modified + class SvnGutterHandler(VcsGutterHandler): def get_vcs_helper(self): @@ -192,3 +266,21 @@ def get_diff_args(self): os.path.join(self.vcs_tree, self.vcs_path), ] return args + + def get_status_args(self): + args = [ + self.exc_path, + 'status', + os.path.join(self.vcs_tree, self.vcs_path), + ] + return args + + def process_status_line(self, st): + st = st[0:1] + if st == 'I': + return 'I' + elif st == '?': + return 'U' + else: + return 'M' + # all other states we may consider as modified diff --git a/icons/ignored.png b/icons/ignored.png new file mode 100755 index 00000000..eed3c3ed Binary files /dev/null and b/icons/ignored.png differ diff --git a/icons/unknown.png b/icons/unknown.png new file mode 100755 index 00000000..7ccda2be Binary files /dev/null and b/icons/unknown.png differ diff --git a/vcs_gutter.py b/vcs_gutter.py index a4c26818..df3c605e 100644 --- a/vcs_gutter.py +++ b/vcs_gutter.py @@ -22,7 +22,8 @@ def plugin_loaded(): class VcsGutterCommand(sublime_plugin.WindowCommand): region_names = ['deleted_top', 'deleted_bottom', - 'deleted_dual', 'inserted', 'changed'] + 'deleted_dual', 'inserted', 'changed', + 'unknown', 'ignored'] def run(self): self.view = self.window.active_view() @@ -31,10 +32,12 @@ def run(self): sublime.set_timeout(self.run, 1) return self.clear_all() - inserted, modified, deleted = ViewCollection.diff(self.view) + inserted, modified, deleted, unknown, ignored = ViewCollection.diff(self.view) self.lines_removed(deleted) self.bind_icons('inserted', inserted) self.bind_icons('changed', modified) + self.bind_icons('unknown', unknown) + self.bind_icons('ignored', ignored) def clear_all(self): for region_name in self.region_names: diff --git a/view_collection.py b/view_collection.py index a75d74f9..826d50a5 100644 --- a/view_collection.py +++ b/view_collection.py @@ -12,6 +12,7 @@ class ViewCollection: views = {} vcs_times = {} + vcs_statuses = {} vcs_files = {} buf_files = {} @@ -69,11 +70,23 @@ def vcs_time(view): ViewCollection.vcs_times[key] = 0 return time.time() - ViewCollection.vcs_times[key] + @staticmethod + def vcs_status(view): + key = ViewCollection.get_key(view) + if not key in ViewCollection.vcs_statuses: + ViewCollection.vcs_statuses[key] = 'M' + return ViewCollection.vcs_statuses[key] + @staticmethod def update_vcs_time(view): key = ViewCollection.get_key(view) ViewCollection.vcs_times[key] = time.time() + @staticmethod + def update_vcs_status(view, status): + key = ViewCollection.get_key(view) + ViewCollection.vcs_statuses[key] = status + @staticmethod def vcs_tmp_file(view): key = ViewCollection.get_key(view)