293 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # coding: utf8
 | |
| import os
 | |
| import re
 | |
| 
 | |
| import sublime
 | |
| import sublime_plugin
 | |
| import difflib
 | |
| import tempfile
 | |
| 
 | |
| from fnmatch import fnmatch
 | |
| import codecs
 | |
| 
 | |
| SETTINGS = sublime.load_settings('FileDiffs.sublime-settings')
 | |
| 
 | |
| CLIPBOARD = u'Diff file with Clipboard'
 | |
| SELECTIONS = u'Diff Selections'
 | |
| SAVED = u'Diff file with Saved'
 | |
| FILE = u'Diff file with File in Project…'
 | |
| TAB = u'Diff file with Open Tab…'
 | |
| 
 | |
| FILE_DIFFS = [CLIPBOARD, SAVED, FILE, TAB]
 | |
| 
 | |
| 
 | |
| class FileDiffMenuCommand(sublime_plugin.TextCommand):
 | |
|     def run(self, edit):
 | |
|         menu_items = FILE_DIFFS[:]
 | |
|         saved = 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, 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')
 | |
| 
 | |
|         if not (self.view.file_name() and self.view.is_dirty()):
 | |
|             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] == CLIPBOARD:
 | |
|                 self.view.run_command('file_diff_clipboard')
 | |
|             elif restored_menu_items[index] == SELECTIONS:
 | |
|                 self.view.run_command('file_diff_selections')
 | |
|             elif restored_menu_items[index] == SAVED:
 | |
|                 self.view.run_command('file_diff_saved')
 | |
|             elif restored_menu_items[index] == FILE:
 | |
|                 self.view.run_command('file_diff_file')
 | |
|             elif restored_menu_items[index] == TAB:
 | |
|                 self.view.run_command('file_diff_tab')
 | |
|         self.view.window().show_quick_panel(menu_items, on_done)
 | |
| 
 | |
| 
 | |
| class FileDiffCommand(sublime_plugin.TextCommand):
 | |
|     def diff_content(self):
 | |
|         content = ''
 | |
| 
 | |
|         regions = [region for region in self.view.sel()]
 | |
|         for region in regions:
 | |
|             if region.empty():
 | |
|                 continue
 | |
|             content += self.view.substr(region)
 | |
| 
 | |
|         if not content:
 | |
|             content = self.view.substr(sublime.Region(0, self.view.size()))
 | |
|         return content
 | |
| 
 | |
|     def run_diff(self, a, b, from_file=None, to_file=None):
 | |
|         from_content = a
 | |
|         to_content = b
 | |
| 
 | |
|         if os.path.exists(a):
 | |
|             if from_file is None:
 | |
|                 from_file = a
 | |
|             with codecs.open(from_file, mode='U', encoding='utf-8') as f:
 | |
|                 from_content = f.readlines()
 | |
|         else:
 | |
|             from_content = a.splitlines(True)
 | |
|             if from_file is None:
 | |
|                 from_file = 'from_file'
 | |
| 
 | |
|         if os.path.exists(b):
 | |
|             if to_file is None:
 | |
|                 to_file = b
 | |
|             with codecs.open(to_file, mode='U', encoding='utf-8') as f:
 | |
|                 to_content = f.readlines()
 | |
|         else:
 | |
|             to_content = b.splitlines(True)
 | |
|             if to_file is None:
 | |
|                 to_file = 'to_file'
 | |
| 
 | |
|         diffs = list(difflib.unified_diff(from_content, to_content, from_file, to_file))
 | |
| 
 | |
|         open_in_sublime = SETTINGS.get('open_in_sublime', True)
 | |
|         external_command = SETTINGS.get('cmd')
 | |
|         if not diffs:
 | |
|             sublime.status_message('No Difference')
 | |
|         else:
 | |
|             if external_command:
 | |
|                 self.diff_with_external(a, b, from_file, to_file)
 | |
| 
 | |
|             if open_in_sublime:
 | |
|                 self.diff_in_sublime(diffs)
 | |
| 
 | |
|     def diff_with_external(self, a, b, from_file=None, to_file=None):
 | |
|         try:
 | |
|             if not os.path.exists(from_file):
 | |
|                 tmp_file = tempfile.NamedTemporaryFile(delete=False)
 | |
|                 from_file = tmp_file.name
 | |
|                 tmp_file.close()
 | |
| 
 | |
|                 with codecs.open(from_file, encoding='utf-8', mode='w+') as tmp_file:
 | |
|                     tmp_file.write(a)
 | |
| 
 | |
|             if not os.path.exists(to_file):
 | |
|                 tmp_file = tempfile.NamedTemporaryFile(delete=False)
 | |
|                 to_file = tmp_file.name
 | |
|                 tmp_file.close()
 | |
| 
 | |
|                 with codecs.open(to_file, encoding='utf-8', mode='w+') as tmp_file:
 | |
|                     tmp_file.write(b)
 | |
| 
 | |
|             if os.path.exists(from_file):
 | |
|                 command = SETTINGS.get('cmd')
 | |
|                 if command is not None:
 | |
|                     command = [c.replace(u'$file1', from_file) for c in command]
 | |
|                     command = [c.replace(u'$file2', to_file) for c in command]
 | |
|                     self.view.window().run_command("exec", {"cmd": command})
 | |
|         except Exception as e:
 | |
|             # some basic logging here, since we are cluttering the /tmp folder
 | |
|             print repr(e)
 | |
|             sublime.status_message(str(e))
 | |
| 
 | |
|     def diff_in_sublime(self, diffs):
 | |
|         scratch = self.view.window().new_file()
 | |
|         scratch.set_scratch(True)
 | |
|         scratch.set_syntax_file('Packages/Diff/Diff.tmLanguage')
 | |
|         scratch_edit = scratch.begin_edit('file_diffs')
 | |
|         scratch.insert(scratch_edit, 0, ''.join(diffs))
 | |
|         scratch.end_edit(scratch_edit)
 | |
| 
 | |
| 
 | |
| class FileDiffClipboardCommand(FileDiffCommand):
 | |
|     def run(self, edit, **kwargs):
 | |
|         current = sublime.get_clipboard()
 | |
|         self.run_diff(self.diff_content(), current,
 | |
|             from_file=self.view.file_name(),
 | |
|             to_file='(clipboard)')
 | |
| 
 | |
| 
 | |
| class FileDiffSelectionsCommand(FileDiffCommand):
 | |
|     def run(self, edit, **kwargs):
 | |
|         regions = self.view.sel()
 | |
|         current = self.view.substr(regions[0])
 | |
|         diff = self.view.substr(regions[1])
 | |
| 
 | |
|         # trim off indent
 | |
|         indent = None
 | |
|         for line in current.splitlines():
 | |
|             new_indent = re.match('[ \t]*', line).group(0)
 | |
|             if new_indent == '':
 | |
|                 continue
 | |
| 
 | |
|             if indent is None:
 | |
|                 indent = new_indent
 | |
|             elif len(new_indent) < len(indent):
 | |
|                 indent = new_indent
 | |
| 
 | |
|             if not indent:
 | |
|                 break
 | |
| 
 | |
|         if indent:
 | |
|             current = u"\n".join(line[len(indent):] for line in current.splitlines())
 | |
| 
 | |
|         # trim off indent
 | |
|         indent = None
 | |
|         for line in diff.splitlines():
 | |
|             new_indent = re.match('[ \t]*', line).group(0)
 | |
|             if new_indent == '':
 | |
|                 continue
 | |
| 
 | |
|             if indent is None:
 | |
|                 indent = new_indent
 | |
|             elif len(new_indent) < len(indent):
 | |
|                 indent = new_indent
 | |
| 
 | |
|         if indent:
 | |
|             diff = u"\n".join(line[len(indent):] for line in diff.splitlines())
 | |
| 
 | |
|         self.run_diff(current, diff,
 | |
|             from_file='first selection',
 | |
|             to_file='second selection')
 | |
| 
 | |
| 
 | |
| class FileDiffSavedCommand(FileDiffCommand):
 | |
|     def run(self, edit, **kwargs):
 | |
|         content = ''
 | |
|         regions = [region for region in self.view.sel()]
 | |
|         for region in regions:
 | |
|             if region.empty():
 | |
|                 continue
 | |
|             content += self.view.substr(region)
 | |
|         if not content:
 | |
|             content = self.view.substr(sublime.Region(0, self.view.size()))
 | |
| 
 | |
|         self.run_diff(self.view.file_name(), content,
 | |
|             from_file=self.view.file_name(),
 | |
|             to_file=self.view.file_name() + u' (Unsaved)')
 | |
| 
 | |
| 
 | |
| class FileDiffFileCommand(FileDiffCommand):
 | |
|     def run(self, edit, **kwargs):
 | |
|         common = None
 | |
|         folders = self.view.window().folders()
 | |
|         files = self.find_files(folders)
 | |
|         for folder in folders:
 | |
|             if common == None:
 | |
|                 common = folder
 | |
|             else:
 | |
|                 common_len = len(common)
 | |
|                 while folder[0:common_len] != common[0:common_len]:
 | |
|                     common_len -= 1
 | |
|                     common = common[0:common_len]
 | |
| 
 | |
|         my_file = self.view.file_name()
 | |
|         # filter out my_file
 | |
|         files = [file for file in files if file != my_file]
 | |
|         # shorten names using common length
 | |
|         file_picker = [file[len(common):] for file in files]
 | |
| 
 | |
|         def on_done(index):
 | |
|             if index > -1:
 | |
|                 self.run_diff(self.diff_content(), files[index],
 | |
|                     from_file=self.view.file_name())
 | |
|         self.view.window().show_quick_panel(file_picker, on_done)
 | |
| 
 | |
|     def find_files(self, folders):
 | |
|         # 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 = [".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"]
 | |
| 
 | |
|         ret = []
 | |
|         for folder in folders:
 | |
|             if not os.path.isdir(folder):
 | |
|                 continue
 | |
| 
 | |
|             for file in os.listdir(folder):
 | |
|                 fullpath = os.path.join(folder, file)
 | |
|                 if os.path.isdir(fullpath):
 | |
|                     # excluded folder?
 | |
|                     if not len([True for pattern in folder_exclude_patterns if fnmatch(file, pattern)]):
 | |
|                         ret += self.find_files([fullpath])
 | |
|                 else:
 | |
|                     # excluded file?
 | |
|                     if not len([True for pattern in file_exclude_patterns if fnmatch(file, pattern)]):
 | |
|                         ret.append(fullpath)
 | |
|         return ret
 | |
| 
 | |
| 
 | |
| class FileDiffTabCommand(FileDiffCommand):
 | |
|     def run(self, edit, **kwargs):
 | |
|         my_id = self.view.id()
 | |
|         files = []
 | |
|         contents = []
 | |
|         untitled_count = 1
 | |
|         for v in self.view.window().views():
 | |
|             if v.id() != my_id:
 | |
|                 this_content = v.substr(sublime.Region(0, v.size()))
 | |
|                 if v.file_name():
 | |
|                     files.append(v.file_name())
 | |
|                 elif v.name():
 | |
|                     files.append(v.name())
 | |
|                 else:
 | |
|                     files.append('untitled %d' % untitled_count)
 | |
|                     untitled_count += 1
 | |
| 
 | |
|                 contents.append(this_content)
 | |
| 
 | |
|         def on_done(index):
 | |
|             if index > -1:
 | |
|                 self.run_diff(self.diff_content(), contents[index],
 | |
|                     from_file=self.view.file_name(),
 | |
|                     to_file=files[index])
 | |
| 
 | |
|         if len(files) == 1:
 | |
|             on_done(0)
 | |
|         else:
 | |
|             menu_items = [os.path.basename(f) for f in files]
 | |
|             self.view.window().show_quick_panel(menu_items, on_done)
 |