6 from cStringIO
import StringIO
10 PREFIX
= os
.path
.realpath(os
.path
.dirname(os
.path
.dirname(sys
.argv
[0])))
11 QMDIR
= os
.path
.join(PREFIX
, 'share', 'ugit', 'qm')
12 ICONSDIR
= os
.path
.join(PREFIX
, 'share', 'ugit', 'icons')
15 'python': 'script.png',
17 'shell': 'script.png',
20 'assembler': 'binary.png',
21 'binary': 'binary.png',
26 def run_cmd(*command
):
27 """Runs a *command argument list and returns the output.
28 e.g. run_cmd("echo", "hello", "world")
31 proc
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
)
33 # Wait for the process to return
34 stdout_value
= proc
.stdout
.read()
39 # Strip off trailing whitespace by default
40 return stdout_value
.rstrip()
43 def get_qm_for_locale(locale
):
44 regex
= re
.compile(r
'([^\.])+\..*$')
45 match
= regex
.match(locale
)
47 locale
= match
.group(1)
49 basename
= locale
.split('_')[0]
51 return os
.path
.join(QMDIR
, basename
+'.qm')
53 def ident_file_type(filename
):
54 '''Returns an icon based on the contents of filename.'''
55 if os
.path
.exists(filename
):
56 fileinfo
= run_cmd('file','-b',filename
)
57 for filetype
, iconname
in KNOWN_FILE_TYPES
.iteritems():
58 if filetype
in fileinfo
.lower():
62 # Fallback for modified files of an unknown type
65 def get_file_icon(filename
):
66 '''Returns the full path to an icon file corresponding to
67 filename's contents.'''
68 icon_file
= ident_file_type(filename
)
69 return get_icon(icon_file
)
71 def get_icon(icon_file
):
72 return os
.path
.join(ICONSDIR
, icon_file
)
75 return subprocess
.Popen(args
).pid
86 def grep(pattern
, items
, squash
=True):
87 isdict
= type(items
) is dict
88 if pattern
in __grep_cache
:
89 regex
= __grep_cache
[pattern
]
91 regex
= __grep_cache
[pattern
] = re
.compile(pattern
)
95 match
= regex
.match(item
)
96 if not match
: continue
97 groups
= match
.groups()
99 subitems
= match
.group(0)
104 subitems
= list(groups
)
106 matchdict
[item
] = items
[item
]
108 matched
.append(subitems
)
113 if squash
and len(matched
) == 1:
119 '''Avoid os.path.basename because we are explicitly
120 parsing git's output, which contains /'s regardless
123 base_regex
= re
.compile('(.*?/)?([^/]+)$')
124 match
= base_regex
.match(path
)
126 return match
.group(2)
130 def shell_quote(*inputs
):
131 '''Quote strings so that they can be suitably martialled
132 off to the shell. This method supports POSIX sh syntax.
133 This is crucial to properly handle command line arguments
134 with spaces, quotes, double-quotes, etc.'''
136 regex
= re
.compile('[^\w!%+,\-./:@^]')
137 quote_regex
= re
.compile("((?:'\\''){2,})")
145 raise AssertionError,('No way to quote strings '
146 'containing null(\\000) bytes')
148 # = does need quoting else in command position it's a
149 # program-local environment setting
150 match
= regex
.search(input)
151 if match
and '=' not in input:
153 input = input.replace("'", "'\\''")
155 # make multiple ' in a row look simpler
156 # '\'''\'''\'' -> '"'''"'
157 quote_match
= quote_regex
.match(input)
159 quotes
= match
.group(1)
160 input.replace(quotes
,
161 ("'" *(len(quotes
)/4)) + "\"'")
163 input = "'%s'" % input
164 if input.startswith("''"):
167 if input.endswith("''"):
174 pad
= HEADER_LENGTH
- len(msg
) - 4 # len(':+') + len('+:')
180 +(' ' *(pad
+ extra
))
184 def parse_geom(geomstr
):
185 regex
= re
.compile('^(\d+)x(\d+)\+(\d+),(\d+)')
186 match
= regex
.match(geomstr
)
188 defaults
.WIDTH
= int(match
.group(1))
189 defaults
.HEIGHT
= int(match
.group(2))
190 defaults
.X
= int(match
.group(3))
191 defaults
.Y
= int(match
.group(4))
193 return (defaults
.WIDTH
, defaults
.HEIGHT
,
194 defaults
.X
, defaults
.Y
)
197 return '%dx%d+%d,%d' % (
205 return os
.path
.basename(defaults
.DIRECTORY
)
213 def write(path
, contents
):
214 file = open(path
, 'w')
218 class DiffParser(object):
219 def __init__( self
, model
,
223 self
.__header
_re
= re
.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
228 self
.__diff
_spans
= []
229 self
.__diff
_offsets
= []
240 with_diff_header
=True,
247 self
.parse_diff(diff
)
249 # Always index into the non-reversed diff
250 self
.fwd_header
, self
.fwd_diff
= \
253 with_diff_header
=True,
258 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
259 if not noop
and which
< len(self
.diffs
):
260 diff
= self
.diffs
[which
]
261 write(filename
, self
.header
+ '\n' + diff
+ '\n')
269 def get_diff_subset(self
, diff
, start
, end
):
274 offset
= self
.__diff
_spans
[diff
][0]
275 diffguts
= '\n'.join(self
.__diffs
[diff
])
277 for line
in self
.__diffs
[diff
]:
278 line_start
= offset
+ local_offset
279 local_offset
+= len(line
) + 1 #\n
280 line_end
= offset
+ local_offset
281 # |line1 |line2 |line3 |
284 # selection has head of diff (line3)
285 if start
< line_start
and end
> line_start
and end
< line_end
:
287 if line
.startswith('+'):
289 if line
.startswith('-'):
291 # selection has all of diff (line2)
292 elif start
<= line_start
and end
>= line_end
:
294 if line
.startswith('+'):
296 if line
.startswith('-'):
298 # selection has tail of diff (line1)
299 elif start
>= line_start
and start
< line_end
- 1:
301 if line
.startswith('+'):
303 if line
.startswith('-'):
306 # Don't add new lines unless selected
307 if line
.startswith('+'):
309 elif line
.startswith('-'):
310 # Don't remove lines unless selected
311 newdiff
.append(' ' + line
[1:])
315 new_count
= self
.__headers
[diff
][1] + adds
- deletes
316 if new_count
!= self
.__headers
[diff
][3]:
317 header
= '@@ -%d,%d +%d,%d @@' % (
318 self
.__headers
[diff
][0],
319 self
.__headers
[diff
][1],
320 self
.__headers
[diff
][2],
324 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
327 return self
.__diff
_spans
329 def get_offsets(self
):
330 return self
.__diff
_offsets
332 def set_diff_to_offset(self
, offset
):
334 self
.diffs
, self
.selected
= self
.get_diff_for_offset(offset
)
336 def set_diffs_to_range(self
, start
, end
):
339 self
.diffs
, self
.selected
= self
.get_diffs_for_range(start
,end
)
341 def get_diff_for_offset(self
, offset
):
342 for idx
, diff_offset
in enumerate(self
.__diff
_offsets
):
343 if offset
< diff_offset
:
344 return (['\n'.join(self
.__diffs
[idx
])], [idx
])
347 def get_diffs_for_range(self
, start
, end
):
350 for idx
, span
in enumerate(self
.__diff
_spans
):
351 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
352 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
353 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
355 selected_diff
=(has_end_of_diff
359 diff
= '\n'.join(self
.__diffs
[idx
])
362 return diffs
, indices
364 def parse_diff(self
, diff
):
369 for idx
, line
in enumerate(diff
.splitlines()):
370 match
= self
.__header
_re
.match(line
)
372 self
.__headers
.append([
378 self
.__diffs
.append( [line
] )
380 line_len
= len(line
) + 1 #\n
381 self
.__diff
_spans
.append([total_offset
,
382 total_offset
+ line_len
])
383 total_offset
+= line_len
384 self
.__diff
_offsets
.append(total_offset
)
388 errmsg
= 'Malformed diff?\n\n%s' % diff
389 raise AssertionError, errmsg
390 line_len
= len(line
) + 1
391 total_offset
+= line_len
393 self
.__diffs
[self
.__idx
].append(line
)
394 self
.__diff
_spans
[-1][-1] += line_len
395 self
.__diff
_offsets
[self
.__idx
] += line_len
397 def process_diff_selection(self
, selected
, offset
, selection
):
399 start
= self
.fwd_diff
.index(selection
)
400 end
= start
+ len(selection
)
401 self
.set_diffs_to_range(start
, end
)
403 self
.set_diff_to_offset(offset
)
405 # Process diff selection only
407 for idx
in self
.selected
:
408 contents
= self
.get_diff_subset(idx
, start
, end
)
410 tmpfile
= self
.model
.get_tmp_filename()
411 write(tmpfile
, contents
)
412 self
.model
.apply_diff(tmpfile
)
414 # Process a complete hunk
416 for idx
, diff
in enumerate(self
.diffs
):
417 tmpfile
= self
.model
.get_tmp_filename()
418 if self
.write_diff(tmpfile
,idx
):
419 self
.model
.apply_diff(tmpfile
)
422 def sanitize_input(input):
423 for c
in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
424 input = input.replace(c
, '_')