📄 filediff.py
字号:
for o in matcher.get_opcodes(): if o[0] == "equal": if (o[2]-o[1] < 3) or (o[4]-o[3] < 3): back = o[4]-o[3], o[2]-o[1] continue for i in range(2): s,e = starts[i].copy(), starts[i].copy() s.forward_chars( o[1+2*i] - back[i] ) e.forward_chars( o[2+2*i] ) bufs[i].apply_tag(tags[i], s, e) back = (0,0) yield 1 def on_textview_expose_event(self, textview, event): if self.num_panes == 1: return if event.window != textview.get_window(gtk.TEXT_WINDOW_TEXT) \ and event.window != textview.get_window(gtk.TEXT_WINDOW_LEFT): return if not hasattr(textview, "meldgc"): self._setup_gcs(textview) visible = textview.get_visible_rect() pane = self.textview.index(textview) start_line = self._pixel_to_line(pane, visible.y) end_line = 1+self._pixel_to_line(pane, visible.y+visible.height) gc = lambda x : getattr(textview.meldgc, "gc_"+x) #gcdark = textview.get_style().black_gc gclight = textview.get_style().bg_gc[gtk.STATE_ACTIVE] #curline = textview.get_buffer().get_iter_at_mark( textview.get_buffer().get_insert() ).get_line() def draw_change(change): # draw background and thin lines ypos0 = self._line_to_pixel(pane, change[1]) - visible.y width = event.window.get_size()[0] #gcline = (gclight, gcdark)[change[1] <= curline and curline < change[2]] gcline = gclight event.window.draw_line(gcline, 0,ypos0-1, width,ypos0-1) if change[2] != change[1]: ypos1 = self._line_to_pixel(pane, change[2]) - visible.y event.window.draw_line(gcline, 0,ypos1, width,ypos1) event.window.draw_rectangle(gc(change[0]), 1, 0,ypos0, width,ypos1-ypos0) last_change = None for change in self.linediffer.single_changes(pane, self._get_texts()): if change[2] < start_line: continue if change[1] > end_line: break if last_change and change[1] <= last_change[2]: last_change = ("conflict", last_change[1], max(last_change[2],change[2])) else: if last_change: draw_change(last_change) last_change = change if last_change: draw_change(last_change) def _get_filename_for_saving(self, title ): dialog = gtk.FileChooserDialog(title, parent=self.widget.get_toplevel(), action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) ) response = dialog.run() filename = None if response == gtk.RESPONSE_OK: filename = dialog.get_filename() dialog.destroy() if filename: if os.path.exists(filename): response = misc.run_dialog( _('"%s" exists!\nOverwrite?') % os.path.basename(filename), parent = self, buttonstype = gtk.BUTTONS_YES_NO) if response == gtk.RESPONSE_NO: return None return filename return None def _save_text_to_filename(self, filename, text): try: open(filename, "w").write(text) except IOError, e: misc.run_dialog( _("Error writing to %s\n\n%s.") % (filename, e), self, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK) return False return True def save_file(self, pane, saveas=0): buf = self.textview[pane].get_buffer() bufdata = self.bufferdata[pane] if saveas or not bufdata.filename: filename = self._get_filename_for_saving( _("Choose a name for buffer %i.") % (pane+1) ) if filename: bufdata.filename = os.path.abspath(filename) self.fileentry[pane].set_filename( bufdata.filename) else: return melddoc.RESULT_ERROR text = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0) if bufdata.newlines: if type(bufdata.newlines) == type(""): if(bufdata.newlines) != '\n': text = text.replace("\n", bufdata.newlines) elif type(bufdata.newlines) == type(()): buttons = {'\n':("UNIX (LF)",0), '\r\n':("DOS (CR-LF)", 1), '\r':("MAC (CR)",2) } newline = misc.run_dialog( _("This file '%s' contains a mixture of line endings.\n\nWhich format would you like to use?") % bufdata.filename, self, gtk.MESSAGE_WARNING, buttonstype=gtk.BUTTONS_CANCEL, extrabuttons=[ buttons[b] for b in bufdata.newlines ] ) if newline < 0: return for k,v in buttons.items(): if v[1] == newline: bufdata.newlines = k if k != '\n': text = text.replace('\n', k) break if bufdata.encoding and self.prefs.save_encoding==0: try: text = text.encode(bufdata.encoding) except UnicodeEncodeError: if misc.run_dialog( _("'%s' contains characters not encodable with '%s'\nWould you like to save as UTF-8?") % (bufdata.filename, bufdata.encoding), self, gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO) != gtk.RESPONSE_YES: return melddoc.RESULT_ERROR if self._save_text_to_filename(bufdata.filename, text): self.emit("file-changed", bufdata.filename) self.undosequence.clear() self.set_buffer_modified(buf, 0) return melddoc.RESULT_OK else: return melddoc.RESULT_ERROR def make_patch(self, pane): fontdesc = pango.FontDescription(self.prefs.get_current_font()) dialog = gnomeglade.Component( paths.share_dir("glade2/filediff.glade"), "patchdialog") dialog.widget.set_transient_for( self.widget.get_toplevel() ) bufs = [t.get_buffer() for t in self.textview] texts = [b.get_text(*b.get_bounds()).split("\n") for b in bufs] texts[0] = [l+"\n" for l in texts[0]] texts[1] = [l+"\n" for l in texts[1]] names = [self._get_filename(i) for i in range(2)] dialog.textview.modify_font(fontdesc) buf = dialog.textview.get_buffer() lines = [] for line in difflib.unified_diff(texts[0], texts[1], names[0], names[1]): buf.insert( buf.get_end_iter(), line ) lines.append(line) result = dialog.widget.run() dialog.widget.destroy() if result >= 0: txt = "".join(lines) if result == 1: # copy clip = gtk.clipboard_get() clip.set_text(txt) clip.store() else:# save as filename = self._get_filename_for_saving( _("Save patch as...") ) if filename: self._save_text_to_filename(filename, txt) def set_buffer_writable(self, buf, yesno): pane = self.textview.index(buf.textview) self.bufferdata[pane].writable = yesno self.recompute_label() def set_buffer_modified(self, buf, yesno): pane = self.textview.index(buf.textview) self.bufferdata[pane].modified = yesno self.recompute_label() def save(self): pane = self._get_focused_pane() if pane >= 0: self.save_file(pane) def save_all(self): for i in range(self.num_panes): if self.bufferdata[i].modified: self.save_file(i) def on_fileentry_activate(self, entry): if self.on_delete_event() == gtk.RESPONSE_OK: files = [ e.get_full_path(0) for e in self.fileentry[:self.num_panes] ] self.set_files(files) return 1 def _get_focused_pane(self): for i in range(self.num_panes): if self.textview[i].is_focus(): return i return -1 def copy_selected(self, direction): assert direction in (-1,1) src_pane = self._get_focused_pane() dst_pane = src_pane + direction assert dst_pane in range(self.num_panes) buffers = [t.get_buffer() for t in self.textview] text = buffers[src_pane].get_text( buffers[src_pane].get_start_iter(), buffers[src_pane].get_end_iter() ) self.on_text_begin_user_action() buffers[dst_pane].set_text( text ) self.on_text_end_user_action() self.scheduler.add_task( lambda : self._sync_vscroll( self.scrolledwindow[src_pane].get_vadjustment() ) and None ) # # refresh and reload # def on_reload_activate(self, *extra): modified = [b.filename for b in self.bufferdata if b.modified] if len(modified): message = _("Reloading will discard changes in:\n%s\n\nYou cannot undo this operation.") % "\n".join(modified) response = misc.run_dialog( message, parent=self, messagetype=gtk.MESSAGE_WARNING, buttonstype=gtk.BUTTONS_OK_CANCEL) if response != gtk.RESPONSE_OK: return files = [b.filename for b in self.bufferdata[:self.num_panes] ] self.set_files(files) def on_refresh_activate(self, *extra): files = [None for b in self.bufferdata[:self.num_panes] ] self.set_files(files) def queue_draw(self, junk=None): for i in range(self.num_panes-1): self.linkmap[i].queue_draw() self.diffmap0.queue_draw() self.diffmap1.queue_draw() # # scrollbars # def _sync_hscroll(self, adjustment): if not hasattr(self,"_sync_hscroll_lock"): self._sync_hscroll_lock = 0 if not self._sync_hscroll_lock: self._sync_hscroll_lock = 1 adjs = map( lambda x: x.get_hadjustment(), self.scrolledwindow) adjs.remove(adjustment) val = adjustment.get_value() for a in adjs: a.set_value(val) self._sync_hscroll_lock = 0 def _sync_vscroll(self, adjustment): # only allow one scrollbar to be here at a time if not hasattr(self,"_sync_vscroll_lock"): self._sync_vscroll_lock = 0 if not self._sync_vscroll_lock: self._sync_vscroll_lock = 1 syncpoint = 0.5 adjustments = map( lambda x: x.get_vadjustment(), self.scrolledwindow) adjustments = adjustments[:self.num_panes] master = adjustments.index(adjustment) # scrollbar influence 0->1->2 or 0<-1<-2 or 0<-1->2 others = zip( range(self.num_panes), adjustments) del others[master] if master == 2: others.reverse() # the line to search for in the 'master' text master_y = adjustment.value + adjustment.page_size * syncpoint it = self.textview[master].get_line_at_y(master_y)[0] line_y, height = self.textview[master].get_line_yrange(it) line = it.get_line() + ((master_y-line_y)/height) for (i,adj) in others: mbegin,mend, obegin,oend = 0, self._get_line_count(master), 0, self._get_line_count(i) # look for the chunk containing 'line' for c in self.linediffer.pair_changes(master, i, self._get_texts()): c = c[1:] if c[0] >= line: mend = c[0] oend = c[2] break elif c[1] >= line: mbegin,mend = c[0],c[1] obegin,oend = c[2],c[3] break else: mbegin = c[1] obegin = c[3] fraction = (line - mbegin) / ((mend - mbegin) or 1) other_line = (obegin + fraction * (oend - obegin)) it = self.textview[i].get_buffer().get_iter_at_line(other_line) val, height = self.textview[i].get_line_yrange(it) val -= (adj.page_size) * syncpoint val += (other_line-int(other_line)) * height val = misc.clamp(val, 0, adj.upper - adj.page_size) adj.set_value( val ) # scrollbar influence 0->1->2 or 0<-1<-2 or 0<-1->2 if master != 1: line = other_line master = 1 self.on_linkmap_expose_event(self.linkmap0, None) self.on_linkmap_expose_event(self.linkmap1, None) self._sync_vscroll_lock = 0 # # diffmap drawing # def on_diffmap_expose_event(self, area, event): diffmapindex = self.diffmap.index(area) textindex = (0, self.num_panes-1)[diffmapindex] #TODO need height of arrow button on scrollbar - how do we get that? size_of_arrow = 14 hperline = float( self.scrolledwindow[textindex].get_allocation().height - 4*size_of_arrow) / self._get_line_count(textindex) if hperline > self.pixels_per_line: hperline = self.pixels_per_line scaleit = lambda x,s=hperline,o=size_of_arrow: x*s+o x0 = 4 x1 = area.get_allocation().width - 2*x0 window = area.window window.clear() gctext = area.get_style().text_gc[0] if not hasattr(area, "meldgc"): self._setup_gcs(area) gc = area.meldgc.get_gc for c in self.linediffer.single_changes(textindex, self._get_texts()): assert c[0] != "equal" s,e = [int(x) for x in ( math.floor(scaleit(c[1])), math.ceil(scaleit(c[2]+(c[1]==c[2]))) ) ] window.draw_rectangle( gc(c[0]), 1, x0, s, x1, e-s) window.draw_rectangle( gctext, 0, x0, s, x1, e-s) def on_diffmap_button_press_event(self, area, event): #TODO need gutter of scrollbar - how do we get that? if event.button == 1: size_of_arrow = 14 diffmapindex = self.diffmap.index(area) index = (0, self.num_panes-1)[diffmapindex] height = area.get_allocation().height fraction = (event.y - size_of_arrow) / (height - 3.75*size_of_arrow) adj = self.scrolledwindow[index].get_vadjustment() val = fraction * adj.upper - adj.page_size/2 upper = adj.upper - adj.page_size adj.set_value( max( min(upper, val), 0) ) return 1 return 0 def _get_line_count(self, index): """Return the number of lines in the buffer of textview 'text'""" return self.textview[index].get_buffer().get_line_count() def set_num_panes(self, n): if n != self.num_panes and n in (1,2,3): self.num_panes = n toshow = self.scrolledwindow[:n] + self.fileentry[:n] toshow += self.linkmap[:n-1] + self.diffmap[:n] map( lambda x: x.show(), toshow ) tohide = self.statusimage + self.scrolledwindow[n:] + self.fileentry[n:]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -