diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/Context.sublime-menu b/Context.sublime-menu index 2cf4afd..c437b46 100644 --- a/Context.sublime-menu +++ b/Context.sublime-menu @@ -4,5 +4,6 @@ { "caption": "Diff Selections", "command": "file_diff_selections" }, { "caption": "Diff with Clipboard", "command": "file_diff_clipboard" }, { "caption": "Diff with Saved", "command": "file_diff_saved" }, + { "caption": "Diff with Previous", "command": "file_diff_previous" }, { "caption": "-" } ] diff --git a/FileDiffs.sublime-settings b/FileDiffs.sublime-settings index 1d8c3b7..bd8730c 100644 --- a/FileDiffs.sublime-settings +++ b/FileDiffs.sublime-settings @@ -1,34 +1,50 @@ -// You can set a command like this -// "cmd": ["command", "$file1", "$file2"] - -// You can also include other command line parameters like this -// "cmd": ["command", "-parameter1", "-parameter2", "$file1", "$file2"] - -{ - // just uncomment one of the examples - // or write your own command - // NOTE: Copy/paste example below or write your own command in: Package Settings --> FileDiffs --> Settings - User - // This file will be overwritten if the package is updated. - - // The "command" argument is different depending on your system, for instance - // you might need to prefix /usr/local/bin, or use another explicit path. - - // opendiff (FileMerge) - // "cmd": ["opendiff", "$file1", "$file2"] - - // ksdiff (Kaleidoscope) - // "cmd": ["ksdiff", "$file1", "$file2"] - - // "open_in_sublime": false - // twdiff (Textwrangler) - // "cmd": ["twdiff", "$file1", "$file2"] - - // bbdiff (BBEdit) NOTE: Use example below if you receive error. - // "cmd": ["bbdiff", "$file1", "$file2"] - - // bbdiff (BBEdit) - // "cmd" ["/usr/local/bin/bbdiff", "$file1", "$file2"] - - // deltawalker (DeltaWalker) - // "cmd": ["deltawalker", "-nosplash", "$file1", "$file2"] -} +// You can set a command like this +// "cmd": ["command", "$file1", "$file2"] + +// You can also include other command line parameters like this +// "cmd": ["command", "-parameter1", "-parameter2", "$file1", "$file2"] + +{ + // just uncomment one of the examples + // or write your own command + // NOTE: Copy/paste example below or write your own command in: Package Settings --> FileDiffs --> Settings - User + // This file will be overwritten if the package is updated. + + // The "command" argument is different depending on your system, for instance + // you might need to prefix /usr/local/bin, or use another explicit path. + + // opendiff (FileMerge) + // "cmd": ["opendiff", "$file1", "$file2"] + + // ksdiff (Kaleidoscope) + // "cmd": ["ksdiff", "$file1", "$file2"] + + // "open_in_sublime": false + // twdiff (Textwrangler) + // "cmd": ["twdiff", "$file1", "$file2"] + + // bbdiff (BBEdit) NOTE: Use example below if you receive error. + // "cmd": ["bbdiff", "$file1", "$file2"] + + // bbdiff (BBEdit) + // "cmd" ["/usr/local/bin/bbdiff", "$file1", "$file2"] + + // deltawalker (DeltaWalker) + // "cmd": ["deltawalker", "-nosplash", "$file1", "$file2"] + + // bcomp (Beyond Compare) + // Install Beyond Compare Command Line Tools: + // Beyond Compare > Install Command Line Tools + // "cmd": ["/usr/local/bin/bcomp", "$file1", "$file2"] + + // "trim_trailing_white_space_before_diff": false + // "expand_full_file_name_in_tab": false + // "apply_tempfile_changes_after_diff_tool": false + // "limit": 1000 + + // Reverses "diff with clipboard" direction. + // "reverse_clipboard": true, + + // Number of context lines. Defaults to 3. For full context, set it as "full". + // "context_lines": 3, +} diff --git a/LICENSE b/LICENSE index 8da8c54..b40776e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012, Colin T.A. Gray +Copyright (c) 2014, Colin T.A. Gray All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Main.sublime-menu b/Main.sublime-menu index edb3be8..a1d1d28 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -15,8 +15,14 @@ "caption": "FileDiffs", "children": [ - { "command": "open_file", "args": {"file": "${packages}/FileDiffs/FileDiffs.sublime-settings"}, "caption": "Settings – Default" }, - { "command": "open_file", "args": {"file": "${packages}/User/FileDiffs.sublime-settings"}, "caption": "Settings – User" }, + { + "command": "edit_settings", + "args": { + "base_file": "${packages}/FileDiffs/FileDiffs.sublime-settings", + "default": "{\n$0\n}\n" + }, + "caption": "Settings" + }, { "caption": "-" } ] } diff --git a/README.md b/README.md index 3aaf462..614cdd2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,16 @@ FileDiffs Plugin Shows diffs between the current file, or selection(s) in the current file, and clipboard, another file, or unsaved changes. Can be configured to show diffs in an external diff tool +### Preview + +![Preview](https://github.com/ildarkhasanshin/SublimeFileDiffs/raw/master/preview_1.png) + +![Preview](https://github.com/ildarkhasanshin/SublimeFileDiffs/raw/master/preview_2.png) + +![Preview](https://github.com/ildarkhasanshin/SublimeFileDiffs/raw/master/preview_3.png) + +-------------- + Help! ----- @@ -13,24 +23,10 @@ Check the [wiki][] for more tips Installation ------------ -1. Using Package Control, install "FileDiffs" - -Or: +Using Package Control, install `FileDiffs` or clone this repo in your packages folder. -1. Open the Sublime Text Packages folder - - OS X: ~/Library/Application Support/Sublime Text 3/Packages/ - - Windows: %APPDATA%/Sublime Text 3/Packages/ - - Linux: ~/.Sublime Text 3/Packages/ or ~/.config/sublime-text-3/Packages - -2. clone this repo -3. Install keymaps for the commands (see Example.sublime-keymap for my preferred keys) - -### Sublime Text 2 - -1. Open the Sublime Text 2 Packages folder -2. clone this repo, but use the `st2` branch - - git clone -b st2 git@github.com:colinta/SublimeFileDiffs +I recommended you add key bindings for the commands. I've included my preferred bindings below. +Copy them to your key bindings file (⌘⇧,). Add External Diff Tool *(optional)* -------- @@ -43,6 +39,10 @@ Add External Diff Tool *(optional)* This command *may* need to be a full path (e.g. `/usr/local/bin/ksdiff`), if the command isn't in your `PATH`. +It supports: + +- A generic setting `FileDiffs.sublime-settings` which could be overloaded for each parameter in a platform specific configuration `FileDiffs ($platform).sublime-settings` in the `Settings - User` +- Environment variable expansions for `cmd` parameter in the settings Commands -------- @@ -60,3 +60,27 @@ The rest of the commands do not need to be bound (accessible from the menu): `file_diff_file`: Shows the diff of the current file or selection(s) and a file that is in the current project. `file_diff_tab`: Shows the diff of the current file or selection(s) and an open file (aka a file that has a tab). + +`file_diff_previous`: Shows the diff of the current file or selection(s) and the previous activated file. If a file is not saved yet, dirty buffer is used instead of reading from disk. + +If FileDiffs has to use temporary files, they are created in your `Data/Packages` folder (rather than system temp folder) due to privacy concerns for portable Sublime Text installations. Temporary files are automatically removed after 15 seconds. + +Key Bindings +------------ + +Copy these to your user key bindings file. + + + { "keys": ["ctrl+shift+d"], "command": "file_diff_menu" }, + { "keys": ["ctrl+shift+e"], "command": "file_diff_menu", "args": {"cmd": ["opendiff", "$file1", "$file2"] } }, + + +Contributors +------------ + +Thanks to: + +- **Sebastian Pape** for adding support for using an external diff tool +- **Starli0n** for merging the ST2 and ST3 branches into one branch, +- and for adding the "Diff file with previous" feature +- **dnsmkl** for helping with diffing temporary files diff --git a/Tab Context.sublime-menu b/Tab Context.sublime-menu index 2a7fc9e..7950291 100644 --- a/Tab Context.sublime-menu +++ b/Tab Context.sublime-menu @@ -1,6 +1,6 @@ [ { "caption": "-" }, { "caption": "FileDiffs Menu", "command": "file_diff_menu" }, - { "caption": "Diff with Tab…", "command": "file_diff_tab" }, + { "caption": "Diff tab with Open Tab…", "command": "file_diff_tab" }, { "caption": "-" } ] diff --git a/file_diffs.py b/file_diffs.py index f9581a0..aa9bdd5 100644 --- a/file_diffs.py +++ b/file_diffs.py @@ -1,68 +1,78 @@ # coding: utf8 import os import re +import sys import sublime import sublime_plugin import difflib import tempfile +import subprocess from fnmatch import fnmatch import codecs -class FileDiffMenuCommand(sublime_plugin.TextCommand): - CLIPBOARD = 'Diff file with Clipboard' - SELECTIONS = 'Diff Selections' - SAVED = 'Diff file with Saved' - FILE = 'Diff file with File in Project…' - TAB = 'Diff file with Open Tab…' +if sublime.platform() == "windows": + from subprocess import Popen - FILE_DIFFS = [CLIPBOARD, SAVED, FILE, TAB] +class FileDiffMenuCommand(sublime_plugin.TextCommand): def run(self, edit, cmd=None): - menu_items = self.FILE_DIFFS[:] - saved = self.SAVED - non_empty_regions = [region for region in self.view.sel() if not region.empty()] - if len(non_empty_regions) == 2: - menu_items.insert(1, self.SELECTIONS) - elif len(non_empty_regions): - menu_items = [f.replace(u'Diff file', u'Diff selection') for f in menu_items] - saved = saved.replace(u'Diff file', u'Diff selection') + # Individual menu items. + CLIPBOARD = {'text': 'Diff file with Clipboard', 'command' : 'file_diff_clipboard'} + SELECTIONS = {'text': 'Diff Selections', 'command' : 'file_diff_selections'} + SAVED = {'text': 'Diff file with Saved', 'command' : 'file_diff_saved'} + FILE = {'text': u'Diff file with File in Project…', 'command' : 'file_diff_file'} + TAB = {'text': u'Diff file with Open Tab…', 'command' : 'file_diff_tab'} + PREVIOUS = {'text': 'Diff file with Previous Tab', 'command' : 'file_diff_previous'} + + menu_items = [CLIPBOARD, SELECTIONS, SAVED, FILE, TAB, PREVIOUS] + + non_empty_regions = len([region for region in self.view.sel() if not region.empty()]) + + if non_empty_regions != 2: + menu_items.remove(SELECTIONS) + + if non_empty_regions and non_empty_regions != 2: + for item in menu_items: + item['text'] = item['text'].replace('Diff file', 'Diff selection') + + if cmd is not None: + for item in menu_items: + item['text'] += ' (' + os.path.splitext(os.path.basename(cmd[0]))[0] + ')' if not (self.view.file_name() and self.view.is_dirty()): - menu_items.remove(saved) + menu_items.remove(SAVED) def on_done(index): - restored_menu_items = [f.replace(u'Diff selection', u'Diff file') for f in menu_items] - if index == -1: - return - elif restored_menu_items[index] == self.CLIPBOARD: - self.view.run_command('file_diff_clipboard', {'cmd': cmd}) - elif restored_menu_items[index] == self.SELECTIONS: - self.view.run_command('file_diff_selections', {'cmd': cmd}) - elif restored_menu_items[index] == self.SAVED: - self.view.run_command('file_diff_saved', {'cmd': cmd}) - elif restored_menu_items[index] == self.FILE: - self.view.run_command('file_diff_file', {'cmd': cmd}) - elif restored_menu_items[index] == self.TAB: - self.view.run_command('file_diff_tab', {'cmd': cmd}) - self.view.window().show_quick_panel(menu_items, on_done) + if index >= 0: + self.view.run_command(menu_items[index]['command'], {'cmd': cmd}) + + self.view.window().show_quick_panel([item['text'] for item in menu_items], on_done) class FileDiffCommand(sublime_plugin.TextCommand): - def settings(self): - return sublime.load_settings('FileDiffs.sublime-settings') + def get_setting(self, key, default=None): + settings = sublime.load_settings('FileDiffs.sublime-settings') + os_specific_settings = {} + if sublime.platform() == 'windows': + os_specific_settings = sublime.load_settings('FileDiffs (Windows).sublime-settings') + elif sublime.platform() == 'osx': + os_specific_settings = sublime.load_settings('FileDiffs (OSX).sublime-settings') + else: + os_specific_settings = sublime.load_settings('FileDiffs (Linux).sublime-settings') + return os_specific_settings.get(key, settings.get(key, default)) - def diff_content(self): + def diff_content(self, view): content = '' - for region in self.view.sel(): + for region in view.sel(): if region.empty(): continue - content += self.view.substr(region) + content += view.substr(region) if not content: - content = self.view.substr(sublime.Region(0, self.view.size())) + content = view.substr(sublime.Region(0, view.size())) return content def prep_content(self, ab, file_name, default_name): @@ -70,65 +80,133 @@ def prep_content(self, ab, file_name, default_name): if file_name is None: file_name = default_name content = [line.replace("\r\n", "\n").replace("\r", "\n") for line in content] + + trim_trailing_white_space_before_diff = self.get_setting('trim_trailing_white_space_before_diff', False) + if trim_trailing_white_space_before_diff: + content = [line.rstrip() for line in content] + return (content, file_name) def run_diff(self, a, b, from_file, to_file, **options): external_diff_tool = options.get('cmd') + if options.get('reverse'): + from_file, to_file = to_file, from_file + a, b = b, a + (from_content, from_file) = self.prep_content(a, from_file, 'from_file') (to_content, to_file) = self.prep_content(b, to_file, 'to_file') - diffs = list(difflib.unified_diff(from_content, to_content, from_file, to_file)) + context_lines = self.get_setting("context_lines", 3); + if context_lines == "full": + context_lines = sys.maxsize + + diffs = list(difflib.unified_diff(from_content, to_content, from_file, to_file, n=context_lines)) if not diffs: - sublime.status_message('No Difference') + self.view.show_popup('No Difference') + else: - external_command = external_diff_tool or self.settings().get('cmd') - open_in_sublime = self.settings().get('open_in_sublime', not external_command) + external_command = external_diff_tool or self.get_setting('cmd') + open_in_sublime = self.get_setting('open_in_sublime', not external_command) if external_command: - self.diff_with_external(external_command, a, b, from_file, to_file) + self.diff_with_external(external_command, a, b, from_file, to_file, **options) if open_in_sublime: # fix diffs diffs = map(lambda line: (line and line[-1] == "\n") and line or line + "\n", diffs) self.diff_in_sublime(diffs) - def diff_with_external(self, external_command, a, b, from_file=None, to_file=None): + def diff_with_external(self, external_command, a, b, from_file=None, to_file=None, **options): try: - try: - from_file_exists = os.path.exists(from_file) - except ValueError: - from_file_exists = False - - try: - to_file_exists = os.path.exists(to_file) - except ValueError: - to_file_exists = False - - if not from_file_exists: - tmp_file = tempfile.NamedTemporaryFile(delete=False) + from_file_on_disk = self.file_will_be_read_from_disk(from_file) + to_file_on_disk = self.file_will_be_read_from_disk(to_file) + + # If a dirty file is diffed against the copy on disk, we statically set + # `from_file_on_disk` to True; so that the diff becomes "file to temp" + # instead of "temp to temp". + if to_file == from_file + " (Unsaved)": + view = self.view.window().find_open_file(from_file) + if os.path.exists(from_file) and view and view.is_dirty(): + from_file_on_disk = True + + files_to_remove = [] + + if not from_file_on_disk: + tmp_file = tempfile.NamedTemporaryFile(dir = sublime.packages_path(), prefix = "file-diffs-", suffix = ".temp", delete=False) from_file = tmp_file.name tmp_file.close() + files_to_remove.append(tmp_file.name) with codecs.open(from_file, encoding='utf-8', mode='w+') as tmp_file: tmp_file.write(a) - if not to_file_exists: - tmp_file = tempfile.NamedTemporaryFile(delete=False) + if not to_file_on_disk: + tmp_file = tempfile.NamedTemporaryFile(dir = sublime.packages_path(), prefix = "file-diffs-", suffix = ".temp", delete=False) to_file = tmp_file.name tmp_file.close() + files_to_remove.append(tmp_file.name) with codecs.open(to_file, encoding='utf-8', mode='w+') as tmp_file: tmp_file.write(b) + trim_trailing_white_space_before_diff = self.get_setting('trim_trailing_white_space_before_diff', False) + if trim_trailing_white_space_before_diff: + def trim_trailing_white_space(file_name): + trim_lines = [] + modified = False + with codecs.open(file_name, encoding='utf-8', mode='r') as f: + lines = f.readlines() + lines = [line.replace("\n", '').replace("\r", '') for line in lines] + for line in lines: + trim_line = line.rstrip() + trim_lines.append(trim_line) + if trim_line != line: + modified = True + if modified: + tmp_file = tempfile.NamedTemporaryFile(dir = sublime.packages_path(), prefix = "file-diffs-", suffix = ".temp", delete=False) + file_name = tmp_file.name + tmp_file.close() + files_to_remove.append(tmp_file.name) + with codecs.open(file_name, encoding='utf-8', mode='w+') as f: + f.writelines('\n'.join(trim_lines)) + return file_name + + from_file = trim_trailing_white_space(from_file) + to_file = trim_trailing_white_space(to_file) + if os.path.exists(from_file): - external_command = [c.replace(u'$file1', from_file) for c in external_command] - external_command = [c.replace(u'$file2', to_file) for c in external_command] - self.view.window().run_command("exec", {"cmd": external_command}) + external_command = [c.replace('$file1', from_file) for c in external_command] + external_command = [c.replace('$file2', to_file) for c in external_command] + external_command = [os.path.expandvars(c) for c in external_command] + if sublime.platform() == "windows": + Popen(external_command) + else: + subprocess.Popen(external_command) + + apply_tempfile_changes_after_diff_tool = self.get_setting('apply_tempfile_changes_after_diff_tool', False) + post_diff_tool = options.get('post_diff_tool') + if apply_tempfile_changes_after_diff_tool and post_diff_tool is not None and (not from_file_on_disk or not to_file_on_disk): + if from_file_on_disk: + from_file = None + if to_file_on_disk: + to_file = None + # Use a dialog to block st and wait for the closing of the diff tool + if sublime.ok_cancel_dialog("Apply changes from tempfile after external diff tool execution?"): + post_diff_tool(from_file, to_file) except Exception as e: # some basic logging here, since we are cluttering the /tmp folder - sublime.status_message(str(e)) + self.view.show_popup(str(e)) + + finally: + def remove_files(): + for file in files_to_remove: + os.remove(file) + + # Remove temp files after 15 seconds. We don't remove immediately, + # because external diff tools may take some time to read them from disk. + sublime.set_timeout_async(remove_files, 15000) def diff_in_sublime(self, diffs): diffs = ''.join(diffs) @@ -138,11 +216,50 @@ def diff_in_sublime(self, diffs): scratch.run_command('file_diff_dummy1', {'content': diffs}) def read_file(self, file_name): - content="" + content = '' with codecs.open(file_name, mode='U', encoding='utf-8') as f: content = f.read() return content + def get_file_name(self, view, default_name): + file_name = '' + if view.file_name(): + file_name = view.file_name() + elif view.name(): + file_name = view.name() + else: + file_name = default_name + return file_name + + def get_content_from_file(self, file_name): + with codecs.open(file_name, encoding='utf-8', mode='r') as f: + lines = f.readlines() + lines = [line.replace("\r\n", "\n").replace("\r", "\n") for line in lines] + content = ''.join(lines) + return content + + def update_view(self, view, edit, tmp_file): + if tmp_file: + non_empty_regions = [region for region in view.sel() if not region.empty()] + nb_non_empty_regions = len(non_empty_regions) + region = None + if nb_non_empty_regions == 0: + region = sublime.Region(0, view.size()) + elif nb_non_empty_regions == 1: + region = non_empty_regions[0] + else: + self.view.show_popup('Cannot update multiselection') + return + view.replace(edit, region, self.get_content_from_file(tmp_file)) + + def file_will_be_read_from_disk(self, file): + view = self.view.window().find_open_file(file) + return os.path.exists(file) and not (view and view.is_dirty()) and not (view and self.view_has_a_selection(view)) + + def view_has_a_selection(self, view): + """Checks if it has exactly one non-empty selection.""" + return len(view.sel()) == 1 and not view.sel()[0].empty() + class FileDiffDummy1Command(sublime_plugin.TextCommand): def run(self, edit, content): @@ -151,19 +268,34 @@ def run(self, edit, content): class FileDiffClipboardCommand(FileDiffCommand): def run(self, edit, **kwargs): + to_file = self.get_file_name(self.view, 'untitled') + for region in self.view.sel(): + if not region.empty(): + to_file += ' (Selection)' + break clipboard = sublime.get_clipboard() - self.run_diff(self.diff_content(), clipboard, - from_file=self.view.file_name(), - to_file='(clipboard)', + def on_post_diff_tool(from_file, to_file): + self.update_view(self.view, edit, to_file) + sublime.set_clipboard(self.get_content_from_file(from_file)) + + reverse = kwargs.get('reverse') or self.get_setting('reverse_clipboard', False) + kwargs.update({'post_diff_tool': on_post_diff_tool, 'reverse': reverse}) + + self.run_diff(clipboard, self.diff_content(self.view), + from_file='(clipboard)', + to_file=to_file, **kwargs) + def is_visible(self): + return bool(sublime.get_clipboard()) + class FileDiffSelectionsCommand(FileDiffCommand): def trim_indent(self, lines): indent = None for line in lines: # ignore blank lines - if line == '': + if not line: continue new_indent = re.match('[ \t]*', line).group(0) @@ -188,34 +320,44 @@ def run(self, edit, **kwargs): # trim off indent indent = self.trim_indent(first_selection.splitlines()) if indent: - first_selection = u"\n".join(line[len(indent):] for line in first_selection.splitlines()) + first_selection = "\n".join(line[len(indent):] for line in first_selection.splitlines()) # trim off indent indent = self.trim_indent(second_selection.splitlines()) if indent: - second_selection = u"\n".join(line[len(indent):] for line in second_selection.splitlines()) + second_selection = "\n".join(line[len(indent):] for line in second_selection.splitlines()) self.run_diff(first_selection, second_selection, from_file='first selection', to_file='second selection', **kwargs) + def is_visible(self): + return len(self.view.sel()) > 1 + class FileDiffSavedCommand(FileDiffCommand): def run(self, edit, **kwargs): - self.run_diff(self.read_file(self.view.file_name()), self.diff_content(), + def on_post_diff_tool(from_file, to_file): + self.update_view(self.view, edit, to_file) + + kwargs.update({'post_diff_tool': on_post_diff_tool}) + self.run_diff(self.read_file(self.view.file_name()), self.diff_content(self.view), from_file=self.view.file_name(), - to_file=self.view.file_name() + u' (Unsaved)', + to_file=self.view.file_name() + ' (Unsaved)', **kwargs) + def is_visible(self): + return bool(self.view.file_name()) and self.view.is_dirty() + class FileDiffFileCommand(FileDiffCommand): def run(self, edit, **kwargs): common = None folders = self.view.window().folders() - files = self.find_files(folders) + files = self.find_files(folders, []) for folder in folders: - if common == None: + if common is None: common = folder else: common_len = len(common) @@ -225,13 +367,13 @@ def run(self, edit, **kwargs): my_file = self.view.file_name() # filter out my_file - files = [file for file in files if file != my_file] + files = [f for f in files if f != my_file] # shorten names using common length - file_picker = [file[len(common):] for file in files] + file_picker = [f[len(common):] for f in files] def on_done(index): if index > -1: - self.run_diff(self.diff_content(), self.read_file(files[index]), + self.run_diff(self.diff_content(self.view), self.read_file(files[index]), from_file=self.view.file_name(), to_file=files[index], **kwargs) @@ -239,28 +381,28 @@ def on_done(index): def find_files(self, folders, ret=[]): # Cannot access these settings!! WHY!? - # folder_exclude_patterns = self.view.settings().get('folder_exclude_patterns') - # file_exclude_patterns = self.view.settings().get('file_exclude_patterns') + # folder_exclude_patterns = self.view.get_setting('folder_exclude_patterns') + # file_exclude_patterns = self.view.get_setting('file_exclude_patterns') folder_exclude_patterns = [".svn", ".git", ".hg", "CVS"] file_exclude_patterns = ["*.pyc", "*.pyo", "*.exe", "*.dll", "*.obj", "*.o", "*.a", "*.lib", "*.so", "*.dylib", "*.ncb", "*.sdf", "*.suo", "*.pdb", "*.idb", ".DS_Store", "*.class", "*.psd", "*.db"] - max_files = self.settings().get('limit', 1000) + max_files = self.get_setting('limit', 1000) for folder in folders: if not os.path.isdir(folder): continue - for file in os.listdir(folder): - fullpath = os.path.join(folder, file) + for f in os.listdir(folder): + fullpath = os.path.join(folder, f) if os.path.isdir(fullpath): # excluded folder? - if not len([True for pattern in folder_exclude_patterns if fnmatch(file, pattern)]): + if not len([True for pattern in folder_exclude_patterns if fnmatch(f, pattern)]): self.find_files([fullpath], ret) else: # excluded file? - if not len([True for pattern in file_exclude_patterns if fnmatch(file, pattern)]): + if not len([True for pattern in file_exclude_patterns if fnmatch(f, pattern)]): ret.append(fullpath) if len(ret) >= max_files: - sublime.status_message('Too many files to include all of them in this list') + self.view.show_popup('Too many files to include all of them in this list') return ret return ret @@ -270,6 +412,7 @@ def run(self, edit, **kwargs): my_id = self.view.id() files = [] contents = [] + views = [] untitled_count = 1 for v in self.view.window().views(): if v.id() != my_id: @@ -283,10 +426,16 @@ def run(self, edit, **kwargs): untitled_count += 1 contents.append(this_content) + views.append(v) def on_done(index): if index > -1: - self.run_diff(self.diff_content(), contents[index], + def on_post_diff_tool(from_file, to_file): + self.update_view(self.view, edit, from_file) + self.update_view(views[index], edit, to_file) + + kwargs.update({'post_diff_tool': on_post_diff_tool}) + self.run_diff(self.diff_content(self.view), contents[index], from_file=self.view.file_name(), to_file=files[index], **kwargs) @@ -294,5 +443,48 @@ def on_done(index): if len(files) == 1: on_done(0) else: - menu_items = [os.path.basename(f) for f in files] + if self.get_setting('expand_full_file_name_in_tab', False): + menu_items = [[os.path.basename(f),f] for f in files] + else: + menu_items = [os.path.basename(f) for f in files] sublime.set_timeout(lambda: self.view.window().show_quick_panel(menu_items, on_done), 1) + + def is_visible(self): + return len(self.view.window().views()) > 1 + + +previous_view = current_view = None + +class FileDiffPreviousCommand(FileDiffCommand): + def run(self, edit, **kwargs): + if previous_view: + def on_post_diff_tool(from_file, to_file): + self.update_view(previous_view, edit, from_file) + self.update_view(current_view, edit, to_file) + + kwargs.update({'post_diff_tool': on_post_diff_tool}) + self.run_diff(self.diff_content(previous_view), self.diff_content(self.view), + from_file=self.get_file_name(previous_view, 'untitled (Previous)'), + to_file=self.get_file_name(self.view, 'untitled (Current)'), + **kwargs) + + def is_visible(self): + return previous_view is not None + +def record_current_view(view): + global previous_view + global current_view + previous_view = current_view + current_view = view + +class FileDiffListener(sublime_plugin.EventListener): + def on_activated(self, view): + try: + # Prevent 'show_quick_panel()' of 'FileDiffs Menu' from being recorded + viewids = [v.id() for v in view.window().views()] + if view.id() not in viewids: + return + if current_view is None or view.id() != current_view.id(): + record_current_view(view) + except AttributeError: + pass diff --git a/package.json b/package.json index 020ac54..5f336bf 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,15 @@ { "name": "FileDiffs", + "details": "https://github.com/colinta/SublimeFileDiffs", "description": "Shows diffs between the current file, or selection(s) in the current file, and clipboard, another file, or unsaved changes. With contributions from Sebastian Pape (spape) and Jiri Urban (jiriurban)", "author": "Colin T.A. Gray (colinta)", - "details": "https://github.com/colinta/SublimeFileDiffs", - "labels": ["diff/merge"], + "labels": [ + "diff/merge" + ], "releases": [ { - "details": "https://github.com/colinta/SublimeFileDiffs/tree/st2", - "sublime_text": "<3000" - }, - { - "details": "https://github.com/colinta/SublimeFileDiffs/tags", - "sublime_text": ">=3000" + "sublime_text": ">=3000", + "tags": true } ] -} \ No newline at end of file +} diff --git a/preview_1.png b/preview_1.png new file mode 100644 index 0000000..04281c5 Binary files /dev/null and b/preview_1.png differ diff --git a/preview_2.png b/preview_2.png new file mode 100644 index 0000000..869a1fc Binary files /dev/null and b/preview_2.png differ diff --git a/preview_3.png b/preview_3.png new file mode 100644 index 0000000..ad15903 Binary files /dev/null and b/preview_3.png differ