687
698
theResources = Resources()
689
def decodefilename(s):
700
# convert a string obtained from the file system or command line to unicode
701
def decode_fs_string(s):
690
702
if s is not None:
692
704
s = unicode(s, 'utf_8')
694
706
s = unicode(s, sys.getfilesystemencoding())
709
# map an encoding name to its standard form
710
def norm_encoding(e):
712
return e.replace('-', '_').lower()
714
# widget to help pick an encoding
715
class EncodingMenu(gtk.HBox):
716
def __init__(self, prefs, autodetect=False):
717
gtk.HBox.__init__(self)
718
self.combobox = combobox = gtk.combo_box_new_text()
719
self.encodings = prefs.getEncodings()[:]
720
for e in self.encodings:
721
combobox.append_text(e)
723
self.encodings.insert(0, None)
724
combobox.prepend_text(_('Auto Detect'))
725
self.pack_start(combobox, False, False, 0)
728
def set_text(self, encoding):
729
encoding = norm_encoding(encoding)
730
if encoding in self.encodings:
731
self.combobox.set_active(self.encodings.index(encoding))
734
i = self.combobox.get_active()
736
return self.encodings[i]
697
738
# text entry widget with a button to help pick file names
698
739
class FileEntry(gtk.HBox):
699
740
def __init__(self, parent, title):
774
822
[ 'Font', 'display_font', 'Monospace 10', _('Font') ],
775
823
[ 'Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024 ],
824
[ 'Boolean', 'display_show_right_margin', False, _('Show right margin') ],
825
[ 'Integer', 'display_right_margin', 80, _('Right margin'), 1, 8192 ],
776
826
[ 'Boolean', 'display_show_line_numbers', True, _('Show line numbers') ],
777
827
[ 'Boolean', 'display_show_whitespace', False, _('Show white space characters') ],
828
[ 'Boolean', 'display_ignore_case', False, _('Ignore case differences') ],
778
829
[ 'Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences') ],
779
830
[ 'Boolean', 'display_ignore_whitespace_changes', False, _('Ignore changes to white space') ],
780
831
[ 'Boolean', 'display_ignore_blanklines', False, _('Ignore blank line differences') ],
781
[ 'Boolean', 'display_ignore_case', False, _('Ignore case differences') ],
782
832
[ 'Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences') ]
836
[ 'Boolean', 'align_ignore_case', False, _('Ignore case') ],
786
837
[ 'Boolean', 'align_ignore_whitespace', True, _('Ignore white space') ],
787
838
[ 'Boolean', 'align_ignore_whitespace_changes', False, _('Ignore changes to white space') ],
788
839
[ 'Boolean', 'align_ignore_blanklines', False, _('Ignore blank lines') ],
789
[ 'Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters') ],
790
[ 'Boolean', 'align_ignore_case', False, _('Ignore case') ]
840
[ 'Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters') ]
794
[ 'Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024 ],
844
[ 'Boolean', 'editor_auto_indent', False, _('Auto indent') ],
795
845
[ 'Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces') ],
796
[ 'Boolean', 'editor_auto_indent', False, _('Auto indent') ]
846
[ 'Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024 ]
804
854
_('Regional Settings'),
806
[ 'String', 'encoding_auto_detect_codecs', ' '.join([sys.getfilesystemencoding(), 'latin_1']), _('Order of codecs used to identify encoding') ]
856
[ 'Encoding', 'encoding_default_codec', sys.getfilesystemencoding(), _('Default codec') ],
857
[ 'String', 'encoding_auto_detect_codecs', ' '.join(auto_detect_codecs), _('Order of codecs used to identify encoding') ]
860
# conditions used to determine if a preference should be greyed out
861
self.disable_when = {
862
'display_right_margin': ('display_show_right_margin', False),
863
'display_ignore_whitespace_changes': ('display_ignore_whitespace', True),
864
'display_ignore_blanklines': ('display_ignore_whitespace', True),
865
'display_ignore_endofline': ('display_ignore_whitespace', True),
866
'align_ignore_whitespace_changes': ('align_ignore_whitespace', True),
867
'align_ignore_blanklines': ('align_ignore_whitespace', True),
868
'align_ignore_endofline': ('align_ignore_whitespace', True)
810
871
root = os.environ.get('SYSTEMDRIVE', None)
905
978
w = self._buildPrefsDialog(parent, widgets, self.template)
979
# disable any preferences than are not relevant
980
for k, v in self.disable_when.items():
982
if widgets[p].get_active() == t:
983
widgets[k].set_sensitive(False)
906
984
dialog.vbox.add(w)
909
987
accept = (dialog.run() == gtk.RESPONSE_OK)
913
for k in self.bool_prefs.keys():
914
ss.append('%s %s\n' % (k, widgets[k].get_active()))
915
for k in self.int_prefs.keys():
916
ss.append('%s %s\n' % (k, widgets[k].get_value_as_int()))
917
for k in self.string_prefs.keys():
918
ss.append('%s "%s"\n' % (k, widgets[k].get_text().replace('\\', '\\\\').replace('"', '\\"')))
920
f = open(self.path, 'w')
921
f.write('# This prefs file was generated by %s %s.\n\n' % (APP_NAME, VERSION))
926
m = MessageDialog(parent, gtk.MESSAGE_ERROR, _('Error writing %s.') % (self.path, ))
929
989
for k in self.bool_prefs.keys():
930
990
self.bool_prefs[k] = widgets[k].get_active()
931
991
for k in self.int_prefs.keys():
932
992
self.int_prefs[k] = widgets[k].get_value_as_int()
933
993
for k in self.string_prefs.keys():
934
994
self.string_prefs[k] = widgets[k].get_text()
997
for k, v in self.bool_prefs.items():
998
if v != self.default_bool_prefs[k]:
999
ss.append('%s %s\n' % (k, v))
1000
for k, v in self.int_prefs.items():
1001
if v != self.default_int_prefs[k]:
1002
ss.append('%s %s\n' % (k, v))
1003
for k, v in self.string_prefs.items():
1004
if v != self.default_string_prefs[k]:
1005
ss.append('%s "%s"\n' % (k, v.replace('\\', '\\\\').replace('"', '\\"')))
1007
f = open(self.path, 'w')
1008
f.write('# This prefs file was generated by %s %s.\n\n' % (APP_NAME, VERSION))
1013
m = MessageDialog(parent, gtk.MESSAGE_ERROR, _('Error writing %s.') % (self.path, ))
935
1016
dialog.destroy()
1019
1103
return self.string_prefs['encoding_auto_detect_codecs'].split()
1021
1105
def getDefaultEncoding(self):
1022
encodings = self._getDefaultEncodings()
1023
if len(encodings) > 0:
1106
return self.string_prefs['encoding_default_codec']
1027
1108
# attempt to convert a string to unicode from an unknown encoding
1028
def convertToUnicode(self, ss):
1109
def convertToUnicode(self, s):
1029
1110
for encoding in self._getDefaultEncodings():
1034
s = unicode(s, encoding)
1036
return result, encoding
1112
return unicode(s, encoding), encoding
1037
1113
except (UnicodeDecodeError, LookupError):
1042
s = ''.join([unichr(ord(c)) for c in s])
1115
return u''.join([ unichr(ord(c)) for c in s ]), None
1046
1117
# cygwin and native applications can be used on windows, use this method
1047
1118
# to convert a path to the usual form expected on sys.platform
1048
1119
def convertToNativePath(self, s):
1049
s = unicode(s, sys.getfilesystemencoding())
1120
s = unicode(s, sys.getfilesystemencoding())
1050
1121
if isWindows() and s.find('/') >= 0:
1051
1122
# treat as a cygwin path
1052
1123
s = s.replace(os.sep, '/')
1471
1553
def getRevision(self, prefs, name, rev):
1472
return popenReadLines(self.root, [ prefs.getString('cvs_bin'), '-Q', 'update', '-p', '-r', rev, safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash')
1554
if rev == 'BASE' and not os.path.exists(name):
1555
# find revision for removed files
1556
for s in popenReadLines(self.root, [ prefs.getString('cvs_bin'), 'status', safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash'):
1557
if s.startswith(' Working revision:\t-'):
1558
rev = s.split('\t')[1][1:]
1559
return popenRead(self.root, [ prefs.getString('cvs_bin'), '-Q', 'update', '-p', '-r', rev, safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash')
1474
1561
# Darcs support
1481
1568
return [ (name, prefs.getString('darcs_default_revision')), (name, None) ]
1483
1570
def getFolderTemplate(self, prefs, names):
1571
fs = _VcsFolderSet(names)
1485
1573
pwd, isabs = os.path.abspath(os.curdir), False
1487
args = [ prefs.getString('darcs_bin'), 'whatsnew', '-l' ]
1575
args = [ prefs.getString('darcs_bin'), 'whatsnew', '-s' ]
1488
1576
for name in names:
1489
1577
isabs |= os.path.isabs(name)
1490
1578
args.append(safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
1491
1579
# build list of interesting files
1492
for s in strip_eols(popenReadLines(self.root, args, prefs, 'darcs_bash')):
1580
for s in popenReadLines(self.root, args, prefs, 'darcs_bash'):
1493
1581
p = s.split(' ')
1494
1582
if len(p) >= 2 and s[0] in 'AMR':
1495
1583
k = prefs.convertToNativePath(p[1])
1496
1584
if not k.endswith(os.sep):
1497
1585
k = os.path.join(self.root, k)
1501
1590
# sort the results
1502
1591
for k in sorted(r.keys()):
1527
1616
return os.path.join(self.root, prefs.convertToNativePath(s.strip()))
1529
1618
def getFolderTemplate(self, prefs, names):
1530
fs = _VcsFolderSet(names)
1620
args = [ prefs.getString('git_bin'), 'status', '--porcelain', '-s', '--untracked-files=no' ]
1621
# build list of interesting files
1532
1622
pwd, isabs = os.path.abspath(os.curdir), False
1533
args = [ prefs.getString('git_bin'), 'status' ]
1534
1623
for name in names:
1535
1624
isabs |= os.path.isabs(name)
1626
prev = prefs.getString('git_default_revision')
1627
fs = _VcsFolderSet(names)
1628
added, modified, removed, renamed = {}, {}, {}, {}
1536
1629
# 'git status' will return 1 when a commit would fail
1537
ss = strip_eols(popenReadLines(self.root, args, prefs, 'git_bash', [0, 1]))
1538
# build list of interesting files
1539
# files may appear in more than one set
1541
unmerged, removed, added, modified, renamed = {}, {}, {}, {}, {}
1545
if s.startswith('# Untracked files:'):
1547
elif s.startswith('#\tunmerged:'):
1630
for s in popenReadLines(self.root, args, prefs, 'git_bash', [0, 1]):
1634
x, y, k = s[0], s[1], s[2:]
1635
if (y == 'D' and x not in 'UD') or (x == 'D' and y == ' '):
1637
k = self._extractPath(k, prefs)
1641
removed[k] = [ (k, prev), (None, None) ]
1642
elif x == 'A' and y not in 'UA':
1644
k = self._extractPath(k, prefs)
1648
added[k] = [ (None, None), (k, None) ]
1653
k0 = self._extractPath(k[0], prefs)
1654
k1 = self._extractPath(k[1], prefs)
1655
if fs.contains(k0) or fs.contains(k1):
1657
k0 = relpath(pwd, k0)
1658
k1 = relpath(pwd, k1)
1659
renamed[k1] = [ (k0, prev), (k1, None) ]
1660
elif x in 'CM' or y == 'M':
1662
k = self._extractPath(k, prefs)
1666
modified[k] = [ (k, prev), (k, None) ]
1548
1668
# merge conflict
1549
k = self._extractPath(s[11:], prefs)
1553
unmerged[k] = [ (k, prefs.getString('git_default_revision')), (k, None), (k, 'MERGE_HEAD') ]
1554
elif s.startswith('#\tdeleted:'):
1556
k = self._extractPath(s[10:], prefs)
1560
removed[k] = [ (k, prefs.getString('git_default_revision')), (None, None) ]
1561
elif s.startswith('#\tnew file:'):
1563
k = self._extractPath(s[11:], prefs)
1567
added[k] = [ (None, None), (k, None) ]
1568
elif s.startswith('#\tmodified:'):
1570
k = self._extractPath(s[11:], prefs)
1574
modified[k] = self.getFileTemplate(prefs, k)
1575
elif s.startswith('#\tboth modified:'):
1577
k = self._extractPath(s[16:], prefs)
1581
unmerged[k] = [ (k, prefs.getString('git_default_revision')), (k, None), (k, ':3') ]
1582
elif s.startswith('#\trenamed:'):
1584
keys = s[10:].split(' -> ')
1586
k0 = self._extractPath(keys[0], prefs)
1587
k1 = self._extractPath(keys[1], prefs)
1588
if fs.contains(k0) or fs.contains(k1):
1590
k0 = relpath(pwd, k0)
1591
k1 = relpath(pwd, k1)
1592
renamed[k1] = [ (k0, prefs.getString('git_default_revision')), (k1, None) ]
1669
k = self._extractPath(k, prefs)
1674
panes = [ (None, None) ]
1676
panes = [ (k, ':2') ]
1677
panes.append((k, None))
1679
panes.append((None, None))
1681
panes.append((k, ':3'))
1682
if x != 'A' and y != 'A':
1683
panes.append((k, ':1'))
1593
1685
# sort the results
1595
for m in unmerged, removed, added, modified, renamed:
1686
result, r = [], set()
1687
for m in added, modified, removed, renamed:
1596
1688
r.update(m.keys())
1597
1689
for k in sorted(r):
1598
for m in unmerged, removed, added, modified, renamed:
1690
for m in removed, added, modified, renamed:
1599
1691
if m.has_key(k):
1600
1692
result.append(m[k])
1601
# break so we open no more than one tab per file
1605
1695
def getRevision(self, prefs, name, rev):
1606
return popenReadLines(self.root, [ prefs.getString('git_bin'), 'show', '%s:%s' % (rev, relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'git_bash')
1696
return popenRead(self.root, [ prefs.getString('git_bin'), 'show', '%s:%s' % (rev, relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'git_bash')
1608
1698
# Mercurial support
1797
1888
k = relpath(pwd, k)
1798
1889
elif line.startswith('head: '):
1799
1890
r[k] = line[6:]
1801
1891
# sort the results
1802
for k in sorted(r.keys()):
1803
result.append([ (k, r[k]), (k, None) ])
1892
return [ [ (k, r[k]), (k, None) ] for k in sorted(r.keys()) ]
1806
1894
def getRevision(self, prefs, name, rev):
1807
return popenReadLines(self.root, [ prefs.getString('rcs_bin_co'), '-p', '-q', '-r' + rev, safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ], prefs, 'rcs_bash')
1895
return popenRead(self.root, [ prefs.getString('rcs_bin_co'), '-p', '-q', '-r' + rev, safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ], prefs, 'rcs_bash')
1809
1897
# Subversion support
1811
1899
def __init__(self, root):
1812
1900
self.root = root
1903
def getURL(self, prefs):
1904
if self.url is None:
1905
args = [ prefs.getString('svn_bin'), 'info' ]
1906
for s in popenReadLines(self.root, args, prefs, 'svn_bash'):
1907
if s.startswith('URL: '):
1814
1912
def getFileTemplate(self, prefs, name):
1815
1913
# merge conflict
1861
1960
result.append(self.getFileTemplate(prefs, k))
1864
result.append( [ (k + '@BASE', prefs.getString('svn_default_revision')), (None, None) ] )
1963
result.append( [ (k, prefs.getString('svn_default_revision')), (None, None) ] )
1865
1964
result.append( [ (None, None), (k, None) ] )
1868
1967
def getRevision(self, prefs, name, rev):
1869
return popenReadLines(self.root, [ prefs.getString('svn_bin'), 'cat', '-r', rev, safeRelativePath(self.root, name, prefs, 'svn_cygwin') ], prefs, 'svn_bash')
1968
if rev in [ 'PREV', 'BASE', 'COMMITTED' ]:
1969
return popenRead(self.root, [ prefs.getString('svn_bin'), 'cat', '%s@%s' % (safeRelativePath(self.root, name, prefs, 'svn_cygwin'), rev) ], prefs, 'svn_bash')
1970
return popenRead(self.root, [ prefs.getString('svn_bin'), 'cat', '%s/%s@%s' % (self.getURL(prefs), relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'), rev) ], prefs, 'svn_bash')
1872
1973
def isSvkManaged(self, name):
2426
# longest common subsequence of unique elements common to 'a' and 'b'
2427
def __patience_subsequence(a, b):
2428
# value unique lines by their order in each list
2429
value_a, value_b = {}, {}
2430
# find unique values in 'a'
2431
for i, s in enumerate(a):
2436
# find unique values in 'b'
2437
for i, s in enumerate(b):
2442
# lay down items in 'b' as if playing patience if the item is unique in
2444
pile, pointers, atob = [], {}, {}
2445
get, append = value_a.get, pile.append
2452
# find appropriate pile for v
2453
start, end = 0, len(pile)
2454
# optimisation as values usually increase
2455
if end and v > pile[-1]:
2459
mid = (start + end) // 2
2464
if start < len(pile):
2469
pointers[v] = pile[start-1]
2470
# examine our piles to determine the longest common subsequence
2473
v, append = pile[-1], result.append
2474
append((v, atob[v]))
2475
while v in pointers:
2477
append((v, atob[v]))
2481
# difflib-style approximation of the longest common subsequence
2482
def __lcs_approx(a, b):
2483
count1, lookup = {}, {}
2484
# count occurances of each element in 'a'
2486
count1[s] = count1.get(s, 0) + 1
2487
# construct a mapping from a element to where it can be found in 'b'
2488
for i, s in enumerate(b):
2493
if set(lookup).intersection(count1):
2494
# we have some common elements
2495
# identify popular entries
2499
for k, v in count1.items():
2504
for k, v in lookup.items():
2505
if 100 * len(v) > n:
2507
# while walk through entries in 'a', incrementally update the list of
2508
# matching subsequences in 'b' and keep track of the longest match
2510
prev_matches, matches, max_length, max_indices = {}, {}, 0, []
2511
for ai, s in enumerate(a):
2514
# we only extend existing previously found matches to avoid
2515
# performance issues
2516
for bi in prev_matches:
2517
if bi + 1 < n and b[bi + 1] == s:
2518
matches[bi] = v = prev_matches[bi] + 1
2519
# check if this is now the longest match
2522
max_indices.append((ai, bi))
2525
max_indices = [ (ai, bi) ]
2527
prev_get = prev_matches.get
2528
for bi in lookup[s]:
2529
matches[bi] = v = prev_get(bi - 1, 0) + 1
2530
# check if this is now the longest match
2533
max_indices.append((ai, bi))
2536
max_indices = [ (ai, bi) ]
2537
prev_matches, matches = matches, {}
2539
# include any popular entries at the beginning
2540
aidx, bidx, nidx = 0, 0, 0
2541
for ai, bi in max_indices:
2545
while ai and bi and a[ai - 1] == b[bi - 1]:
2550
aidx, bidx, nidx = ai, bi, n
2551
return aidx, bidx, nidx
2553
# patinence diff with difflib-style fallback
2554
def patience_diff(a, b):
2555
matches, len_a, len_b = [], len(a), len(b)
2557
blocks = [ (0, len_a, 0, len_b, 0) ]
2559
start_a, end_a, start_b, end_b, match_idx = blocks.pop()
2560
aa, bb = a[start_a:end_a], b[start_b:end_b]
2562
pivots = __patience_subsequence(aa, bb)
2564
offset_a, offset_b = start_a, start_b
2565
for pivot_a, pivot_b in pivots:
2568
if start_a <= pivot_a:
2570
idx_a, idx_b = pivot_a, pivot_b
2571
while start_a < idx_a and start_b < idx_b and a[idx_a - 1] == b[idx_b - 1]:
2574
# if anything is before recurse on the section
2575
if start_a < idx_a and start_b < idx_b:
2576
blocks.append((start_a, idx_a, start_b, idx_b, match_idx))
2578
start_a, start_b = pivot_a + 1, pivot_b + 1
2579
while start_a < end_a and start_b < end_b and a[start_a] == b[start_b]:
2583
matches.insert(match_idx, (idx_a, idx_b, start_a - idx_a))
2585
# if anything is after recurse on the section
2586
if start_a < end_a and start_b < end_b:
2587
blocks.append((start_a, end_a, start_b, end_b, match_idx))
2589
# fallback if patience fails
2590
pivots = __lcs_approx(aa, bb)
2592
idx_a, idx_b, n = pivots
2595
# if anything is before recurse on the section
2596
if start_a < idx_a and start_b < idx_b:
2597
blocks.append((start_a, idx_a, start_b, idx_b, match_idx))
2599
matches.insert(match_idx, (idx_a, idx_b, n))
2603
# if anything is after recurse on the section
2604
if idx_a < end_a and idx_b < end_b:
2605
blocks.append((idx_a, end_a, idx_b, end_b, match_idx))
2606
# try matching from begining to first match block
2608
end_a, end_b = matches[0][:2]
2610
end_a, end_b = len_a, len_b
2612
while i < end_a and i < end_b and a[i] == b[i]:
2615
matches.insert(0, (0, 0, i))
2616
# try matching from last match block to end
2618
start_a, start_b, n = matches[-1]
2622
start_a, start_b = 0, 0
2623
end_a, end_b = len_a, len_b
2624
while start_a < end_a and start_b < end_b and a[end_a - 1] == b[end_b - 1]:
2628
matches.append((end_a, end_b, len_a - end_a))
2629
# add a zero length block to the end
2630
matches.append((len_a, len_b, 0))
2324
2633
# widget used to compare and merge text files
2325
2634
class FileDiffViewer(gtk.Table):
2326
2635
# class describing a text pane
5260
5588
# 'first_difference' action
5261
5589
def first_difference(self):
5262
5590
self.setLineMode()
5263
self.goto_difference(0, 1)
5591
self.go_to_difference(0, 1)
5265
5593
# 'previous_difference' action
5266
5594
def previous_difference(self):
5267
5595
self.setLineMode()
5268
5596
i = min(self.current_line, self.selection_line) - 1
5269
self.goto_difference(i, -1)
5597
self.go_to_difference(i, -1)
5271
5599
# 'next_difference' action
5272
5600
def next_difference(self):
5273
5601
self.setLineMode()
5274
5602
i = max(self.current_line, self.selection_line) + 1
5275
self.goto_difference(i, 1)
5603
self.go_to_difference(i, 1)
5277
5605
# 'last_difference' action
5278
5606
def last_difference(self):
5279
5607
self.setLineMode()
5280
5608
i = len(self.panes[self.current_pane].lines)
5281
self.goto_difference(i, -1)
5609
self.go_to_difference(i, -1)
5283
5611
# Undo for changes to the pane ordering
5284
5612
class SwapPanesUndo:
6627
6978
[_('_Next Difference'), self.button_cb, 'next_difference', gtk.STOCK_GO_DOWN, 'next_difference'],
6628
6979
[_('_Last Difference'), self.button_cb, 'last_difference', gtk.STOCK_GOTO_BOTTOM, 'last_difference'],
6981
[_('Fir_st Tab'), self.first_tab_cb, None, None, 'first_tab'],
6630
6982
[_('Pre_vious Tab'), self.previous_tab_cb, None, None, 'previous_tab'],
6631
6983
[_('Next _Tab'), self.next_tab_cb, None, None, 'next_tab'],
6984
[_('Las_t Tab'), self.last_tab_cb, None, None, 'last_tab'],
6633
6986
[_('Shift Pane _Right'), self.button_cb, 'shift_pane_right', None, 'shift_pane_right'],
6634
6987
[_('Shift Pane _Left'), self.button_cb, 'shift_pane_left', None, 'shift_pane_left'] ] ])
6951
7304
elif len(items) == 1 and len(items[0][1]) == 1:
6952
7305
# one file specified
6953
7306
# determine which other files to compare it with
6954
name, data = items[0]
7307
name, data, label = items[0]
6955
7308
rev, encoding = data[0]
6956
7309
vcs = theVCSs.findByFilename(name, self.prefs)
6957
7310
if vcs is None:
6958
7311
# shift the existing file so it will be in the second pane
6959
7312
specs.append(FileInfo())
6960
specs.append(FileInfo(name, encoding))
7313
specs.append(FileInfo(name, encoding, None, None, label))
6962
7315
if rev is None:
6963
7316
# no revision specified assume defaults
6964
7317
for name, rev in vcs.getFileTemplate(self.prefs, name):
6965
specs.append(FileInfo(name, encoding, vcs, rev))
7322
specs.append(FileInfo(name, encoding, vcs, rev, s))
6967
7324
# single revision specified
6968
7325
specs.append(FileInfo(name, encoding, vcs, rev))
6969
specs.append(FileInfo(name, encoding))
7326
specs.append(FileInfo(name, encoding, None, None, label))
6971
7328
# multiple files specified, use one pane for each file
6972
for name, data in items:
7329
for name, data, label in items:
6973
7330
for rev, encoding in data:
6974
7331
if rev is None:
7332
vcs, s = None, label
6977
vcs = theVCSs.findByFilename(name, self.prefs)
6978
specs.append(FileInfo(name, encoding, vcs, rev))
7334
vcs, s = theVCSs.findByFilename(name, self.prefs), None
7335
specs.append(FileInfo(name, encoding, vcs, rev, s))
6980
7337
# open a new viewer
6981
7338
viewer = self.newFileDiffViewer(max(2, len(specs)))
6988
7345
# create a new viewer for 'items'
6989
def createSingleTab(self, items):
7346
def createSingleTab(self, items, labels, options):
6990
7347
if len(items) > 0:
6991
self.newLoadedFileDiffViewer(items)
7348
self.newLoadedFileDiffViewer(assign_file_labels(items, labels)).setOptions(options)
6993
7350
# create a new viewer for each item in 'items'
6994
def createSeparateTabs(self, items):
6996
self.newLoadedFileDiffViewer([ item ])
7351
def createSeparateTabs(self, items, labels, options):
7352
# all tabs inherit the first tab's revision and encoding specifications
7353
items = [ (name, items[0][1]) for name, data in items ]
7354
for item in assign_file_labels(items, labels):
7355
self.newLoadedFileDiffViewer([ item ]).setOptions(options)
6998
7357
# create a new viewer for each modified file found in 'items'
6999
def createModifiedFileTabs(self, items):
7358
def createModifiedFileTabs(self, items, labels, options):
7001
7360
for item in items:
7004
if os.path.isfile(dn):
7005
dn = os.path.dirname(dn)
7362
# get full path to an existing ancessor directory
7363
dn = os.path.abspath(name)
7364
while not os.path.isdir(dn):
7365
dn, old_dn = os.path.dirname(dn), dn
7006
7368
if len(new_items) == 0 or dn != new_items[-1][0]:
7007
7369
new_items.append([ dn, None, [] ])
7008
7370
dst = new_items[-1]
7371
dst[1] = data[-1][1]
7011
7373
for dn, encoding, names in new_items:
7012
7374
vcs = theVCSs.findByFolder(dn, self.prefs)
7013
7375
if vcs is not None:
7213
7576
if self.menu_update_depth == 0 and widget.get_active():
7214
7577
self.getCurrentViewer().setSyntax(data)
7579
# callback for the first tab menu item
7580
def first_tab_cb(self, widget, data):
7581
self.notebook.set_current_page(0)
7216
7583
# callback for the previous tab menu item
7217
7584
def previous_tab_cb(self, widget, data):
7218
i = self.notebook.get_current_page() - 1
7220
self.notebook.set_current_page(i)
7585
i, n = self.notebook.get_current_page(), self.notebook.get_n_pages()
7586
self.notebook.set_current_page((n + i - 1) % n)
7222
7588
# callback for the next tab menu item
7223
7589
def next_tab_cb(self, widget, data):
7224
i = self.notebook.get_current_page() + 1
7225
n = self.notebook.get_n_pages()
7227
self.notebook.set_current_page(i)
7590
i, n = self.notebook.get_current_page(), self.notebook.get_n_pages()
7591
self.notebook.set_current_page((i + 1) % n)
7593
# callback for the last tab menu item
7594
def last_tab_cb(self, widget, data):
7595
self.notebook.set_current_page(self.notebook.get_n_pages() - 1)
7229
7597
# callback for most menu items and buttons
7230
7598
def button_cb(self, widget, data):
7302
7670
gobject.signal_new('save', Diffuse.FileDiffViewer.PaneHeader, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
7303
7671
gobject.signal_new('save_as', Diffuse.FileDiffViewer.PaneHeader, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
7673
# create nested subdirectories and return the complete path
7674
def make_subdirs(p, ss):
7676
p = os.path.join(p, s)
7677
if not os.path.exists(p):
7305
7684
# process the command line arguments
7306
7685
if __name__ == '__main__':
7307
# find the config directory
7308
rc_dir = os.environ.get('XDG_CONFIG_HOME', None)
7686
# find the config directory and create it if it didn't exist
7687
rc_dir, subdirs = os.environ.get('XDG_CONFIG_HOME', None), ['diffuse']
7309
7688
if rc_dir is None:
7310
rc_dir = os.path.join(os.path.expanduser('~'), '.config')
7311
for rc_dir in rc_dir, os.path.join(rc_dir, 'diffuse'):
7312
# create the directory if it didn't exist
7313
if not os.path.exists(rc_dir):
7689
rc_dir = os.path.expanduser('~')
7690
subdirs.insert(0, '.config')
7691
rc_dir = make_subdirs(rc_dir, subdirs)
7692
# find the local data directory and create it if it didn't exist
7693
data_dir, subdirs = os.environ.get('XDG_DATA_HOME', None), ['diffuse']
7694
if data_dir is None:
7695
data_dir = os.path.expanduser('~')
7696
subdirs[:0] = [ '.local', 'share' ]
7697
data_dir = make_subdirs(data_dir, subdirs)
7319
7698
# load resource files
7322
7700
if argc == 2 and args[1] == '--no-rcfile':
7324
7702
elif argc == 3 and args[1] == '--rcfile':
7379
7757
encoding = args[i]
7380
7758
encoding = encodings.aliases.aliases.get(encoding, encoding)
7381
7759
elif arg in [ '-m', '--modified' ]:
7760
funcs[mode](specs, labels, options)
7761
specs, labels, options = [], [], {}
7384
7762
mode = 'modified'
7385
7763
elif i + 1 < argc and arg in [ '-r', '--revision' ]:
7386
7764
# specified revision
7388
revs.append((args[i], encoding))
7766
revs.append((decode_fs_string(args[i]), encoding))
7389
7767
elif arg in [ '-s', '--separate' ]:
7768
funcs[mode](specs, labels, options)
7769
specs, labels, options = [], [], {}
7392
7770
# open items in separate tabs
7393
7771
mode = 'separate'
7394
elif i + 1 < argc and arg in [ '-t', '--tab' ]:
7772
elif arg in [ '-t', '--tab' ]:
7773
funcs[mode](specs, labels, options)
7774
specs, labels, options = [], [], {}
7397
7775
# start a new tab
7398
7776
mode = 'single'
7399
7777
elif arg in [ '-b', '--ignore-space-change' ]: