Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 787 lines (628 sloc) 28.307 kb
9071593 @stdk Non-UTF console workaround.
stdk authored
1 # -*- coding: utf-8 -*-
d64beac @gornostal added more logging
authored
2
3 from __future__ import print_function
4
3e4be6e @gornostal init
authored
5 import sublime
6 import sublime_plugin
7 import os
8 import threading
9 import subprocess
10 import functools
11 import re
2e35762 @gornostal Refactored show_status_list()
authored
12 from copy import copy
3e4be6e @gornostal init
authored
13
87a2421 @gornostal fixed issue with icons in new version of ST3
authored
14 IS_ST3 = sublime.version().startswith('3')
15
2bb7435 @gornostal Fixed indentation
authored
16
5e2baa9 @gornostal ST3 support
authored
17 def get_settings():
18 return sublime.load_settings("Modific.sublime-settings")
3e4be6e @gornostal init
authored
19
20
b7d4e14 @gornostal fixed issue #7
authored
21 def get_vcs_settings():
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
22 """
23 Returns list of dictionaries
24 each dict. represents settings for VCS
25 """
b305f34 @gornostal minor changes
authored
26
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
27 default = [
28 {"name": "git", "dir": ".git", "cmd": "git"},
29 {"name": "svn", "dir": ".svn", "cmd": "svn"},
30 {"name": "bzr", "dir": ".bzr", "cmd": "bzr"},
31 {"name": "hg", "dir": ".hg", "cmd": "hg"},
32 {"name": "tf", "dir": "$tf", "cmd": "C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/TF.exe"}
33 ]
34 settings = get_settings().get('vcs', default)
35
36 # re-format settings array if user has old format of settings
37 if type(settings[0]) == list:
38 settings = [dict(name=name, cmd=cmd, dir='.'+name) for name, cmd in settings]
39
40 return settings
41
b305f34 @gornostal minor changes
authored
42
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
43 def get_user_command(vcs_name):
44 """
45 Returns command that user specified for vcs_name
46 """
47
48 try:
49 return [vcs['cmd'] for vcs in get_vcs_settings() if vcs.get('name') == vcs_name][0]
50 except IndexError:
51 return None
b7d4e14 @gornostal fixed issue #7
authored
52
b305f34 @gornostal minor changes
authored
53
a8d716e @KindDragon TFS support added
KindDragon authored
54 def tfs_root(directory):
55 try:
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
56 tf_cmd = get_user_command('tf') or 'tf'
57 command = [tf_cmd, 'workfold', directory]
a8d716e @KindDragon TFS support added
KindDragon authored
58 p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
e305b9b @gornostal changed universal_newlines to False
authored
59 shell=True, universal_newlines=False)
a8d716e @KindDragon TFS support added
KindDragon authored
60 out, err = p.communicate()
61 m = re.search(r"^ \$\S+: (\S+)$", out, re.MULTILINE)
62 if m:
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
63 return {'root': m.group(1), 'name': 'tf', 'cmd': tf_cmd}
b305f34 @gornostal minor changes
authored
64 except:
65 return None
b7d4e14 @gornostal fixed issue #7
authored
66
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
67
68 def get_vcs(directory):
9071593 @stdk Non-UTF console workaround.
stdk authored
69 """
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
70 Determines root directory for VCS and which of VCS systems should be used for a given directory
71
72 Returns dictionary {name: .., root: .., cmd: .., dir: ..}
9071593 @stdk Non-UTF console workaround.
stdk authored
73 """
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
74
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
75 vcs_check = [(lambda vcs: lambda dir: os.path.exists(os.path.join(dir, vcs.get('dir', False)))
76 and vcs)(vcs) for vcs in get_vcs_settings()]
cad7cc8 @stdk To allow runtime config changes, vcs checker function moved back to g…
stdk authored
77
a8d716e @KindDragon TFS support added
KindDragon authored
78 start_directory = directory
3e4be6e @gornostal init
authored
79 while directory:
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
80 available = list(filter(bool, [check(directory) for check in vcs_check]))
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
81 if available:
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
82 available[0]['root'] = directory
83 return available[0]
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
84
3e4be6e @gornostal init
authored
85 parent = os.path.realpath(os.path.join(directory, os.path.pardir))
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
86 if parent == directory: # /.. == /
87 # try TFS as a last resort
b305f34 @gornostal minor changes
authored
88 # I'm not sure why we need to do this. Seems like it should find root for TFS in the main loop
89 return tfs_root(start_directory)
3e4be6e @gornostal init
authored
90 directory = parent
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
91
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
92 return None
3e4be6e @gornostal init
authored
93
94
95 def main_thread(callback, *args, **kwargs):
96 # sublime.set_timeout gets used to send things onto the main thread
97 # most sublime.[something] calls need to be on the main thread
98 sublime.set_timeout(functools.partial(callback, *args, **kwargs), 0)
99
100
5e2baa9 @gornostal ST3 support
authored
101 def _make_text_safeish(text, fallback_encoding, method='decode'):
3e4be6e @gornostal init
authored
102 # The unicode decode here is because sublime converts to unicode inside
103 # insert in such a way that unknown characters will cause errors, which is
104 # distinctly non-ideal... and there's no way to tell what's coming out of
105 # git in output. So...
106 try:
5e2baa9 @gornostal ST3 support
authored
107 unitext = getattr(text, method)('utf-8')
108 except (UnicodeEncodeError, UnicodeDecodeError):
109 unitext = getattr(text, method)(fallback_encoding)
110 except AttributeError:
111 # strongly implies we're already unicode, but just in case let's cast
112 # to string
113 unitext = str(text)
114 return unitext
115
3e4be6e @gornostal init
authored
116
117 def do_when(conditional, callback, *args, **kwargs):
118 if conditional():
119 return callback(*args, **kwargs)
120 sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50)
121
122
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
123 def log(*args, **kwargs):
124 """
125 @param *args: string arguments that should be logged to console
126 @param debug=True: debug log mode
127 @param settings=None: instance of sublime.Settings
128 """
129 debug = kwargs.get('debug', True)
130 settings = kwargs.get('settings', None)
131
d64beac @gornostal added more logging
authored
132 if not settings:
133 settings = get_settings()
134
135 if debug and not settings.get('debug', False):
136 return
137
138 print('Modific:', *args)
139
140
3e4be6e @gornostal init
authored
141 class CommandThread(threading.Thread):
d64beac @gornostal added more logging
authored
142
9071593 @stdk Non-UTF console workaround.
stdk authored
143 def __init__(self, command, on_done, working_dir="", fallback_encoding="", console_encoding="", **kwargs):
3e4be6e @gornostal init
authored
144 threading.Thread.__init__(self)
145 self.command = command
146 self.on_done = on_done
147 self.working_dir = working_dir
5e2baa9 @gornostal ST3 support
authored
148 if 'stdin' in kwargs:
149 self.stdin = kwargs['stdin'].encode()
150 else:
151 self.stdin = None
152 self.stdout = kwargs.get('stdout', subprocess.PIPE)
9071593 @stdk Non-UTF console workaround.
stdk authored
153 self.console_encoding = console_encoding
3e4be6e @gornostal init
authored
154 self.fallback_encoding = fallback_encoding
155 self.kwargs = kwargs
156
157 def run(self):
158 try:
159 # Per http://bugs.python.org/issue8557 shell=True is required to
160 # get $PATH on Windows. Yay portable code.
0d1001d @gornostal Revert "ST3 MacOS fix unicode decode error for utf-8 files"
authored
161 shell = os.name == 'nt'
1a37fc4 @gornostal Fixed bug in the pull request
authored
162
3e4be6e @gornostal init
authored
163 if self.working_dir != "":
164 os.chdir(self.working_dir)
165
9071593 @stdk Non-UTF console workaround.
stdk authored
166 if self.console_encoding:
167 self.command = [s.encode(self.console_encoding) for s in self.command]
168
3e4be6e @gornostal init
authored
169 proc = subprocess.Popen(self.command,
2bb7435 @gornostal Fixed indentation
authored
170 stdout=self.stdout, stderr=subprocess.STDOUT,
171 stdin=subprocess.PIPE,
e305b9b @gornostal changed universal_newlines to False
authored
172 shell=shell, universal_newlines=False)
3e4be6e @gornostal init
authored
173 output = proc.communicate(self.stdin)[0]
174 if not output:
175 output = ''
176 # if sublime's python gets bumped to 2.7 we can just do:
177 # output = subprocess.check_output(self.command)
178 main_thread(self.on_done,
2bb7435 @gornostal Fixed indentation
authored
179 _make_text_safeish(output, self.fallback_encoding), **self.kwargs)
5e2baa9 @gornostal ST3 support
authored
180 except subprocess.CalledProcessError as e:
3e4be6e @gornostal init
authored
181 main_thread(self.on_done, e.returncode)
5e2baa9 @gornostal ST3 support
authored
182 except OSError as e:
3e4be6e @gornostal init
authored
183 if e.errno == 2:
184 main_thread(sublime.error_message,
2bb7435 @gornostal Fixed indentation
authored
185 "'%s' binary could not be found in PATH\n\nConsider using `vcs` property to specify PATH\n\nPATH is: %s" % (self.command[0], os.environ['PATH']))
3e4be6e @gornostal init
authored
186 else:
187 raise e
188
189
5e2baa9 @gornostal ST3 support
authored
190 class EditViewCommand(sublime_plugin.TextCommand):
191
a1654da @gornostal fixed issue with inserting and replacing regions
authored
192 def run(self, edit, command=None, output='', begin=0, region=None):
193 """
194 For some reason Sublime's view.run_command() doesn't allow to pass tuples,
195 therefore region must be a list
196 """
197 region = sublime.Region(int(region[0]), int(region[1])) if region else None
5e2baa9 @gornostal ST3 support
authored
198 if command == 'insert':
a1654da @gornostal fixed issue with inserting and replacing regions
authored
199 self.view.insert(edit, int(begin), output)
5e2baa9 @gornostal ST3 support
authored
200 elif command == 'replace':
201 self.view.replace(edit, region, output)
a1654da @gornostal fixed issue with inserting and replacing regions
authored
202 elif command == 'erase':
5e2baa9 @gornostal ST3 support
authored
203 self.view.erase(edit, region)
a1654da @gornostal fixed issue with inserting and replacing regions
authored
204 else:
205 print('Invalid command: ', command)
206 raise
5e2baa9 @gornostal ST3 support
authored
207
208
46dcf30 @gornostal fixed stupid typos (replaced 'cvs' with 'vcs')
authored
209 class VcsCommand(object):
3e4be6e @gornostal init
authored
210 may_change_files = False
211
a1654da @gornostal fixed issue with inserting and replacing regions
authored
212 def __init__(self, *args, **kwargs):
5e2baa9 @gornostal ST3 support
authored
213 self.settings = get_settings()
a1654da @gornostal fixed issue with inserting and replacing regions
authored
214 super(VcsCommand, self).__init__(*args, **kwargs)
5e2baa9 @gornostal ST3 support
authored
215
d64beac @gornostal added more logging
authored
216 def log(self, *args, **kwargs):
217 return log(settings=self.settings, *args, **kwargs)
218
0d7c063 @gornostal Do not show cvs command on the status bar (Fixed #56)
authored
219 def run_command(self, command, callback=None, show_status=False,
2bb7435 @gornostal Fixed indentation
authored
220 filter_empty_args=True, **kwargs):
3e4be6e @gornostal init
authored
221 if filter_empty_args:
222 command = [arg for arg in command if arg]
223 if 'working_dir' not in kwargs:
224 kwargs['working_dir'] = self.get_working_dir()
225 if 'fallback_encoding' not in kwargs and self.active_view() and self.active_view().settings().get('fallback_encoding'):
226 kwargs['fallback_encoding'] = self.active_view().settings().get('fallback_encoding').rpartition('(')[2].rpartition(')')[0]
5e2baa9 @gornostal ST3 support
authored
227 kwargs['console_encoding'] = self.settings.get('console_encoding')
3e4be6e @gornostal init
authored
228
5e2baa9 @gornostal ST3 support
authored
229 autosave = self.settings.get('autosave', True)
b74a8a3 @gornostal Added `autosave` option to config
authored
230 if self.active_view() and self.active_view().is_dirty() and autosave:
3e4be6e @gornostal init
authored
231 self.active_view().run_command('save')
232 if not callback:
233 callback = self.generic_done
234
6239e66 @gornostal moved log() to the main thread
authored
235 log('run command:', ' '.join(command))
3e4be6e @gornostal init
authored
236 thread = CommandThread(command, callback, **kwargs)
237 thread.start()
238
239 if show_status:
240 message = kwargs.get('status_message', False) or ' '.join(command)
2bb7435 @gornostal Fixed indentation
authored
241 sublime.status_message(message + 'wef')
3e4be6e @gornostal init
authored
242
243 def generic_done(self, result):
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
244 self.log('generic_done', result)
3e4be6e @gornostal init
authored
245 if self.may_change_files and self.active_view() and self.active_view().file_name():
246 if self.active_view().is_dirty():
247 result = "WARNING: Current view is dirty.\n\n"
248 else:
249 # just asking the current file to be re-opened doesn't do anything
5e2baa9 @gornostal ST3 support
authored
250 print("reverting")
3e4be6e @gornostal init
authored
251 position = self.active_view().viewport_position()
252 self.active_view().run_command('revert')
d64beac @gornostal added more logging
authored
253 do_when(lambda: not self.active_view().is_loading(),
254 lambda: self.active_view().set_viewport_position(position, False))
3e4be6e @gornostal init
authored
255
256 if not result.strip():
257 return
258 self.panel(result)
259
260 def _output_to_view(self, output_file, output, clear=False,
2bb7435 @gornostal Fixed indentation
authored
261 syntax="Packages/Diff/Diff.tmLanguage"):
3e4be6e @gornostal init
authored
262 output_file.set_syntax_file(syntax)
263 if clear:
a1654da @gornostal fixed issue with inserting and replacing regions
authored
264 output_file.run_command('edit_view', dict(command='replace', region=[0, self.output_view.size()], output=output))
5e2baa9 @gornostal ST3 support
authored
265 else:
a1654da @gornostal fixed issue with inserting and replacing regions
authored
266 output_file.run_command('edit_view', dict(command='insert', output=output))
3e4be6e @gornostal init
authored
267
268 def scratch(self, output, title=False, position=None, **kwargs):
269 scratch_file = self.get_window().new_file()
270 if title:
271 scratch_file.set_name(title)
272 scratch_file.set_scratch(True)
273 self._output_to_view(scratch_file, output, **kwargs)
274 scratch_file.set_read_only(True)
275 if position:
276 sublime.set_timeout(lambda: scratch_file.set_viewport_position(position), 0)
277 return scratch_file
278
279 def panel(self, output, **kwargs):
280 if not hasattr(self, 'output_view'):
281 self.output_view = self.get_window().get_output_panel("vcs")
282 self.output_view.set_read_only(False)
283 self._output_to_view(self.output_view, output, clear=True, **kwargs)
284 self.output_view.set_read_only(True)
285 self.get_window().run_command("show_panel", {"panel": "output.vcs"})
286
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
287 def _active_file_name(self):
288 view = self.active_view()
289 if view and view.file_name() and len(view.file_name()) > 0:
290 return view.file_name()
3e4be6e @gornostal init
authored
291
292 def active_view(self):
293 return self.view
294
295 def get_window(self):
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
296 if (hasattr(self, 'view') and hasattr(self.view, 'window')):
297 return self.view.window()
298 else:
299 return sublime.active_window()
3e4be6e @gornostal init
authored
300
301 def get_working_dir(self):
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
302 return os.path.dirname(self._active_file_name())
3e4be6e @gornostal init
authored
303
304 def is_enabled(self):
decc2ef @gornostal minor bugfix
authored
305 file_name = self._active_file_name()
306 if file_name and os.path.exists(file_name):
5e2baa9 @gornostal ST3 support
authored
307 return bool(get_vcs(self.get_working_dir()))
308 return False
3e4be6e @gornostal init
authored
309
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
310
311 class DiffCommand(VcsCommand):
312 """ Here you can define diff commands for your VCS
313 method name pattern: %(vcs_name)s_diff_command
314 """
315
3e4be6e @gornostal init
authored
316 def run(self, edit):
317 vcs = get_vcs(self.get_working_dir())
897b355 @gornostal bug fix #27
authored
318 filepath = self.view.file_name()
319 filename = os.path.basename(filepath)
5e2baa9 @gornostal ST3 support
authored
320 max_file_size = self.settings.get('max_file_size', 1024) * 1024
27f60cd @gornostal fix for #28
authored
321 if not os.path.exists(filepath) or os.path.getsize(filepath) > max_file_size:
091ac4c @gornostal New setting 'max_file_size'
authored
322 # skip large files
323 return
42e3acd @gornostal Fixed issue #10
authored
324 get_command = getattr(self, '{0}_diff_command'.format(vcs['name']), None)
325 if get_command:
326 self.run_command(get_command(filename), self.diff_done)
3e4be6e @gornostal init
authored
327
328 def diff_done(self, result):
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
329 self.log('diff_done', result)
3e4be6e @gornostal init
authored
330
331 def git_diff_command(self, file_name):
23d1163 @gornostal new option: "vcs_options"
authored
332 vcs_options = self.settings.get('vcs_options', {}).get('git') or ['--no-color', '--no-ext-diff']
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
333 return [get_user_command('git') or 'git', 'diff'] + vcs_options + ['--', file_name]
3e4be6e @gornostal init
authored
334
335 def svn_diff_command(self, file_name):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
336 params = [get_user_command('svn') or 'svn', 'diff']
23d1163 @gornostal new option: "vcs_options"
authored
337 params.extend(self.settings.get('vcs_options', {}).get('svn', []))
338
339 if '--internal-diff' not in params and self.settings.get('svn_use_internal_diff', True):
f567a43 @gornostal Added option "svn_use_internal_diff' (issue #20)
authored
340 params.append('--internal-diff')
23d1163 @gornostal new option: "vcs_options"
authored
341
342 # if file starts with @, use `--revision HEAD` option
343 # https://github.com/gornostal/Modific/issues/17
d118bcc @gornostal One more fix for the issue #17
authored
344 if file_name.find('@') != -1:
57a45e4 @gornostal Fix for issue #17
authored
345 file_name += '@'
346 params.extend(['--revision', 'HEAD'])
23d1163 @gornostal new option: "vcs_options"
authored
347
348 params.append(file_name)
57a45e4 @gornostal Fix for issue #17
authored
349 return params
3e4be6e @gornostal init
authored
350
aa94fa6 @juracy Bazaar support
juracy authored
351 def bzr_diff_command(self, file_name):
23d1163 @gornostal new option: "vcs_options"
authored
352 vcs_options = self.settings.get('vcs_options', {}).get('bzr', [])
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
353 return [get_user_command('bzr') or 'bzr', 'diff'] + vcs_options + [file_name]
aa94fa6 @juracy Bazaar support
juracy authored
354
3e4be6e @gornostal init
authored
355 def hg_diff_command(self, file_name):
23d1163 @gornostal new option: "vcs_options"
authored
356 vcs_options = self.settings.get('vcs_options', {}).get('hg', [])
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
357 return [get_user_command('hg') or 'hg', 'diff'] + vcs_options + [file_name]
3e4be6e @gornostal init
authored
358
a8d716e @KindDragon TFS support added
KindDragon authored
359 def tf_diff_command(self, file_name):
360 vcs_options = self.settings.get('vcs_options', {}).get('tf') or ['-format:unified']
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
361 return [get_user_command('tf') or 'tf', 'diff'] + vcs_options + [file_name]
a8d716e @KindDragon TFS support added
KindDragon authored
362
d910b21 @gornostal Fixes #79 - Take into account `view.settings().get(default_line_endin…
authored
363 def join_lines(self, lines):
364 """
365 Join lines using os.linesep.join(), unless another method is specified in ST settings
366 """
367 le = self.view.settings().get('default_line_ending')
368 if self.settings.get('debug'):
369 print("use line endings:", le)
370
371 if le == 'windows':
372 return "\r\n".join(lines)
373 elif le == 'unix':
374 return "\n".join(lines)
375
376 return os.linesep.join(lines)
377
3e4be6e @gornostal init
authored
378
379 class ShowDiffCommand(DiffCommand, sublime_plugin.TextCommand):
380 def diff_done(self, result):
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
381 self.log('on show_diff', result)
d64beac @gornostal added more logging
authored
382
3e4be6e @gornostal init
authored
383 if not result.strip():
384 return
385
386 file_name = re.findall(r'([^\\\/]+)$', self.view.file_name())
02cee2f @gornostal some enhancements
authored
387 self.scratch(result, title="Diff - " + file_name[0])
3e4be6e @gornostal init
authored
388
389
390 class DiffParser(object):
391 instance = None
392
393 def __init__(self, diff):
394 self.diff = diff
395 self.chunks = None
396 self.__class__.instance = self
397
398 def _append_to_chunks(self, start, lines):
399 self.chunks.append({
400 "start": start,
401 "end": start + len(lines),
402 "lines": lines
403 })
404
405 def get_chunks(self):
406 if self.chunks is None:
407 self.chunks = []
408 diff = self.diff.strip()
409 if diff:
410 re_header = re.compile(r'^@@[0-9\-, ]+\+(\d+)', re.S)
411 current = None
412 lines = []
413 for line in diff.splitlines():
414 # ignore lines with '\' at the beginning
cce37ff @gornostal Fix for issue #33
authored
415 if line.startswith('\\'):
3e4be6e @gornostal init
authored
416 continue
417
418 matches = re.findall(re_header, line)
419 if matches:
420 if current is not None:
421 self._append_to_chunks(current, lines)
422 current = int(matches[0])
423 lines = []
424 elif current:
425 lines.append(line)
426 if current is not None and lines:
427 self._append_to_chunks(current, lines)
428
429 return self.chunks
430
431 def get_lines_to_hl(self):
432 inserted = []
433 changed = []
434 deleted = []
435
436 for chunk in self.get_chunks():
437 current = chunk['start']
438 deleted_line = None
439 for line in chunk['lines']:
c11b17b @gornostal replaced line[0] with line.startswith()
authored
440 if line.startswith('-'):
3e4be6e @gornostal init
authored
441 if (not deleted_line or deleted_line not in deleted):
442 deleted.append(current)
443 deleted_line = current
c11b17b @gornostal replaced line[0] with line.startswith()
authored
444 elif line.startswith('+'):
3e4be6e @gornostal init
authored
445 if deleted_line:
446 deleted.pop()
447 deleted_line = None
448 changed.append(current)
449 elif current - 1 in changed:
450 changed.append(current)
451 else:
452 inserted.append(current)
453 current += 1
454 else:
455 deleted_line = None
456 current += 1
457
458 return inserted, changed, deleted
459
460 def get_original_part(self, line_num):
461 """ returns a chunk of code that relates to the given line
462 and was there before modifications
463
464 return (lines list, start_line int, replace_lines int)
465 """
466
d057d13 @gornostal fixed command Replace Modified Part
authored
467 # for each chunk from diff:
3e4be6e @gornostal init
authored
468 for chunk in self.get_chunks():
d057d13 @gornostal fixed command Replace Modified Part
authored
469 # if line_num is within that chunk
3e4be6e @gornostal init
authored
470 if chunk['start'] <= line_num <= chunk['end']:
471 ret_lines = []
d057d13 @gornostal fixed command Replace Modified Part
authored
472 current = chunk['start'] # line number that corresponds to current version of file
473 first = None # number of the first line to change
474 replace_lines = 0 # number of lines to change
475 return_this_lines = False # flag shows whether we can return accumulated lines
3e4be6e @gornostal init
authored
476 for line in chunk['lines']:
c11b17b @gornostal replaced line[0] with line.startswith()
authored
477 if line.startswith('-') or line.startswith('+'):
3e4be6e @gornostal init
authored
478 first = first or current
479 if current == line_num:
480 return_this_lines = True
c11b17b @gornostal replaced line[0] with line.startswith()
authored
481 if line.startswith('-'):
d057d13 @gornostal fixed command Replace Modified Part
authored
482 # if line starts with '-' we have previous version
3e4be6e @gornostal init
authored
483 ret_lines.append(line[1:])
484 else:
d057d13 @gornostal fixed command Replace Modified Part
authored
485 # if line starts with '+' we only increment numbers
3e4be6e @gornostal init
authored
486 replace_lines += 1
487 current += 1
488 elif return_this_lines:
489 break
490 else:
d057d13 @gornostal fixed command Replace Modified Part
authored
491 # gap between modifications
492 # reset our variables
3e4be6e @gornostal init
authored
493 current += 1
d057d13 @gornostal fixed command Replace Modified Part
authored
494 first = current
495 replace_lines = 0
3e4be6e @gornostal init
authored
496 ret_lines = []
497 if return_this_lines:
498 return ret_lines, first, replace_lines
499
500 return None, None, None
501
502
503 class HlChangesCommand(DiffCommand, sublime_plugin.TextCommand):
504 def hl_lines(self, lines, hl_key):
3c0b112 @marksteve Added setting to toggle highlighting of changes
marksteve authored
505 if (not len(lines) or not self.settings.get('highlight_changes')):
3e4be6e @gornostal init
authored
506 self.view.erase_regions(hl_key)
507 return
508
5e2baa9 @gornostal ST3 support
authored
509 icon = self.settings.get('region_icon') or 'modific'
a449113 @gornostal add custom icons
authored
510 if icon == 'modific':
87a2421 @gornostal fixed issue with icons in new version of ST3
authored
511 if IS_ST3:
512 icon = 'Packages/Modific/icons/' + hl_key + '.png'
513 else:
514 icon = '../Modific/icons/' + hl_key
3e4be6e @gornostal init
authored
515 points = [self.view.text_point(l - 1, 0) for l in lines]
516 regions = [sublime.Region(p, p) for p in points]
2bb7435 @gornostal Fixed indentation
authored
517 self.view.add_regions(hl_key, regions, "markup.%s.diff" % hl_key, icon, sublime.HIDDEN | sublime.DRAW_EMPTY)
3e4be6e @gornostal init
authored
518
519 def diff_done(self, diff):
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
520 self.log('on hl_changes:', diff)
d64beac @gornostal added more logging
authored
521
02cee2f @gornostal some enhancements
authored
522 if diff and '@@' not in diff:
523 # probably this is an error message
999629e @gornostal if print raise UnicodeEncodeError, try to encode string to utf-8 (iss…
authored
524 # if print raise UnicodeEncodeError, try to encode string to utf-8 (issue #35)
525 try:
5e2baa9 @gornostal ST3 support
authored
526 print(diff)
999629e @gornostal if print raise UnicodeEncodeError, try to encode string to utf-8 (iss…
authored
527 except UnicodeEncodeError:
5e2baa9 @gornostal ST3 support
authored
528 print(diff.encode('utf-8'))
02cee2f @gornostal some enhancements
authored
529
3e4be6e @gornostal init
authored
530 diff_parser = DiffParser(diff)
531 (inserted, changed, deleted) = diff_parser.get_lines_to_hl()
532
e77d0ec @gornostal Fixed #83 - Modific on ST2 was broken because of the syntax error
authored
533 self.log('new lines:', inserted)
534 self.log('modified lines:', changed)
535 self.log('deleted lines:', deleted)
d64beac @gornostal added more logging
authored
536
3e4be6e @gornostal init
authored
537 self.hl_lines(inserted, 'inserted')
538 self.hl_lines(deleted, 'deleted')
539 self.hl_lines(changed, 'changed')
540
541
542 class ShowOriginalPartCommand(DiffCommand, sublime_plugin.TextCommand):
543 def run(self, edit):
544 diff_parser = DiffParser.instance
545 if not diff_parser:
546 return
547
548 (row, col) = self.view.rowcol(self.view.sel()[0].begin())
549 (lines, start, replace_lines) = diff_parser.get_original_part(row + 1)
550 if lines is not None:
d910b21 @gornostal Fixes #79 - Take into account `view.settings().get(default_line_endin…
authored
551 self.panel(self.join_lines(lines))
3e4be6e @gornostal init
authored
552
553
554 class ReplaceModifiedPartCommand(DiffCommand, sublime_plugin.TextCommand):
555 def run(self, edit):
556 self.view.run_command('save')
557
558 diff_parser = DiffParser.instance
559 if not diff_parser:
560 return
561
562 (row, col) = self.view.rowcol(self.view.sel()[0].begin())
563 (lines, current, replace_lines) = diff_parser.get_original_part(row + 1)
5e2baa9 @gornostal ST3 support
authored
564 if self.settings.get('debug'):
565 print('replace', (lines, current, replace_lines))
3e4be6e @gornostal init
authored
566 if lines is not None:
5e2baa9 @gornostal ST3 support
authored
567 begin = self.view.text_point(current - 1, 0)
d910b21 @gornostal Fixes #79 - Take into account `view.settings().get(default_line_endin…
authored
568 content = self.join_lines(lines)
5e2baa9 @gornostal ST3 support
authored
569 if replace_lines:
570 end = self.view.line(self.view.text_point(replace_lines + current - 2, 0)).end()
571 region = sublime.Region(begin, end)
572 if lines:
a1654da @gornostal fixed issue with inserting and replacing regions
authored
573 self.view.run_command('edit_view', dict(command='replace', region=[region.begin(), region.end()], output=content))
3e4be6e @gornostal init
authored
574 else:
5e2baa9 @gornostal ST3 support
authored
575 region = self.view.full_line(region)
a1654da @gornostal fixed issue with inserting and replacing regions
authored
576 self.view.run_command('edit_view', dict(command='erase', region=[region.begin(), region.end()]))
5e2baa9 @gornostal ST3 support
authored
577 else:
a1654da @gornostal fixed issue with inserting and replacing regions
authored
578 self.view.run_command('edit_view', dict(command='insert', begin=begin, output=content + os.linesep))
5e2baa9 @gornostal ST3 support
authored
579 self.view.run_command('save')
3e4be6e @gornostal init
authored
580
581
582 class HlChangesBackground(sublime_plugin.EventListener):
583 def on_load(self, view):
d50c47c @3v1n0 Use async events to run commands in Sublime Text 3
3v1n0 authored
584 if not IS_ST3:
585 view.run_command('hl_changes')
586
587 def on_load_async(self, view):
3e4be6e @gornostal init
authored
588 view.run_command('hl_changes')
589
590 def on_activated(self, view):
d50c47c @3v1n0 Use async events to run commands in Sublime Text 3
3v1n0 authored
591 if not IS_ST3:
592 view.run_command('hl_changes')
593
594 def on_activated_async(self, view):
3e4be6e @gornostal init
authored
595 view.run_command('hl_changes')
596
597 def on_post_save(self, view):
d50c47c @3v1n0 Use async events to run commands in Sublime Text 3
3v1n0 authored
598 if not IS_ST3:
599 view.run_command('hl_changes')
600
601 def on_post_save_async(self, view):
3e4be6e @gornostal init
authored
602 view.run_command('hl_changes')
e6289c3 @gornostal added keyboard shortcuts to go through changed lines #11
authored
603
604
605 class JumpBetweenChangesCommand(DiffCommand, sublime_plugin.TextCommand):
606 def run(self, edit, direction='next'):
607 lines = self._get_lines()
608 if not lines:
609 return
610
611 if direction == 'prev':
612 lines.reverse()
613
614 (current_line, col) = self.view.rowcol(self.view.sel()[0].begin())
615 current_line += 1
616 jump_to = None
617 for line in lines:
618 if direction == 'next' and current_line < line:
619 jump_to = line
620 break
621 if direction == 'prev' and current_line > line:
622 jump_to = line
623 break
624
625 if not jump_to:
626 jump_to = lines[0]
627
628 self.goto_line(edit, jump_to)
629
630 def goto_line(self, edit, line):
631 # Convert from 1 based to a 0 based line number
632 line = int(line) - 1
633
634 # Negative line numbers count from the end of the buffer
635 if line < 0:
636 lines, _ = self.view.rowcol(self.view.size())
637 line = lines + line + 1
638
639 pt = self.view.text_point(line, 0)
640
641 self.view.sel().clear()
642 self.view.sel().add(sublime.Region(pt))
643
644 self.view.show(pt)
645
646 def _get_lines(self):
647 diff_parser = DiffParser.instance
648 if not diff_parser:
649 return
650
651 (inserted, changed, deleted) = diff_parser.get_lines_to_hl()
652 lines = list(set(inserted + changed + deleted))
653 lines.sort()
654
655 prev = None
656 ret_lines = []
657 for line in lines:
658 if prev != line - 1:
659 ret_lines.append(line)
660 prev = line
661
662 return ret_lines
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
663
664
665 class UncommittedFilesCommand(VcsCommand, sublime_plugin.WindowCommand):
666 def active_view(self):
667 return self.window.active_view()
668
fb79fd6 @faube Show uncommitted: show uncommitted files from an opened folder if the…
faube authored
669 def is_enabled(self):
670 return bool(self.get_working_dir())
671
672 def get_working_dir(self):
673 if self._active_file_name():
674 working_dir = super(UncommittedFilesCommand, self).get_working_dir()
675 if working_dir and get_vcs(working_dir):
676 return working_dir
677
678 # If the user has opened a vcs folder, use it.
679 folders = self.window.folders()
680 for folder in folders:
681 if folder and os.path.exists(folder) and get_vcs(folder):
682 return folder
683
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
684 def run(self):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
685 self.vcs = get_vcs(self.get_working_dir())
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
686 status_command = getattr(self, '{0}_status_command'.format(self.vcs['name']), None)
687 if status_command:
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
688 self.run_command(status_command(), self.status_done, working_dir=self.vcs['root'])
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
689
690 def git_status_command(self):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
691 return [get_user_command('git') or 'git', 'status', '--porcelain']
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
692
693 def svn_status_command(self):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
694 return [get_user_command('svn') or 'svn', 'status', '--quiet']
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
695
aa94fa6 @juracy Bazaar support
juracy authored
696 def bzr_status_command(self):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
697 return [get_user_command('bzr') or 'bzr', 'status', '-S', '--no-pending', '-V']
aa94fa6 @juracy Bazaar support
juracy authored
698
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
699 def hg_status_command(self):
8d0d647 @gornostal Added few fixes to pull request #69; changed vcs settings format
authored
700 return [get_user_command('hg') or 'hg', 'status']
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
701
a8d716e @KindDragon TFS support added
KindDragon authored
702 def tf_status_command(self):
140fd19 @gornostal Changed status command for TFS
authored
703 return [get_user_command('tf') or 'tf', 'status']
a8d716e @KindDragon TFS support added
KindDragon authored
704
705 def filter_unified_status(self, result):
706 return list(filter(lambda x: len(x) > 0 and not x.lstrip().startswith('>'),
b305f34 @gornostal minor changes
authored
707 result.rstrip().split('\n')))
a8d716e @KindDragon TFS support added
KindDragon authored
708
709 def git_filter_status(self, result):
710 return self.filter_unified_status(result)
711
712 def svn_filter_status(self, result):
713 return self.filter_unified_status(result)
714
715 def bzr_filter_status(self, result):
716 return self.filter_unified_status(result)
717
718 def hg_filter_status(self, result):
719 return self.filter_unified_status(result)
720
721 def tf_filter_status(self, result):
722 filtered = []
140fd19 @gornostal Changed status command for TFS
authored
723 can_add = False
a8d716e @KindDragon TFS support added
KindDragon authored
724 for line in result.split('\n'):
140fd19 @gornostal Changed status command for TFS
authored
725 if line.startswith('$'):
726 can_add = True
727 continue
728 if line == '':
729 can_add = False
730 continue
731 if can_add:
732 filtered.append(line)
733
a8d716e @KindDragon TFS support added
KindDragon authored
734 return filtered
735
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
736 def git_status_file(self, file_name):
737 # first 2 characters are status codes, the third is a space
738 return file_name[3:]
739
740 def svn_status_file(self, file_name):
741 return file_name[8:]
742
aa94fa6 @juracy Bazaar support
juracy authored
743 def bzr_status_file(self, file_name):
744 return file_name[4:]
745
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
746 def hg_status_file(self, file_name):
747 return file_name[2:]
748
a8d716e @KindDragon TFS support added
KindDragon authored
749 def tf_status_file(self, file_name):
140fd19 @gornostal Changed status command for TFS
authored
750 try:
751 # assume that file name should always contain colon
752 return re.findall(r'\s+(\S+:.+)$', file_name)[0]
753 except:
754 return None
a8d716e @KindDragon TFS support added
KindDragon authored
755
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
756 def status_done(self, result):
a8d716e @KindDragon TFS support added
KindDragon authored
757 filter_status = getattr(self, '{0}_filter_status'.format(self.vcs['name']), None)
758 self.results = filter_status(result)
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
759 if len(self.results):
760 self.show_status_list()
761 else:
762 sublime.status_message("Nothing to show")
763
764 def show_status_list(self):
2e35762 @gornostal Refactored show_status_list()
authored
765 options = copy(self.results)
766 options.insert(0, " - Open All")
767 self.get_window().show_quick_panel(options, self.panel_done, sublime.MONOSPACE_FONT)
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
768
769 def panel_done(self, picked):
288167c @kubudi Add open all uncommited files.
kubudi authored
770 if picked == 0:
2e35762 @gornostal Refactored show_status_list()
authored
771 self.open_files(*self.results)
288167c @kubudi Add open all uncommited files.
kubudi authored
772 return
773 elif 0 > picked < len(self.results):
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
774 return
288167c @kubudi Add open all uncommited files.
kubudi authored
775 picked_file = self.results[picked - 1]
2e35762 @gornostal Refactored show_status_list()
authored
776 self.open_files(picked_file)
e7561d8 @gornostal New feature: view uncommitted files in a quick panel
authored
777
2e35762 @gornostal Refactored show_status_list()
authored
778 def open_files(self, *files):
779 for f in files:
288167c @kubudi Add open all uncommited files.
kubudi authored
780 get_file = getattr(self, '{0}_status_file'.format(self.vcs['name']), None)
2e35762 @gornostal Refactored show_status_list()
authored
781 if get_file:
782 fname = get_file(f)
783 if os.path.isfile(os.path.join(self.vcs['root'], fname)):
784 self.window.open_file(os.path.join(self.vcs['root'], fname))
785 else:
786 sublime.status_message("File '{0}' doesn't exist".format(fname))
Something went wrong with that request. Please try again.