def merge_wdiff2(self, i): """Return content for wdiff by line (2 files)""" (tag, i1, i2, j1, j2, k1, k2, mode, row, bf) = self.opcodes[i] logger.debug( "merge_wdiff2: tag={} a[{}:{}]/b[{}:{}]/c[{}:{}] mode={}, row={}, bf='{}'" .format(tag, i1, i2, j1, j2, k1, k2, mode, row, bf)) line_a = "".join(self.list_a[i1:i2]) line_b = "".join(self.list_b[j1:j2]) if self.isjunk: isjunk = None else: isjunk = lambda x: x in " \t" seq = SequenceMatcher(isjunk, line_a, line_b, None) opcodes = seq.get_opcodes() line = "" for tag, wi1, wi2, wj1, wj2 in opcodes: if tag == "equal": line += line_a[wi1:wi2] else: # other tags line += self.ws0 line += line_a[wi1:wi2] line += self.ws1 line += line_b[wj1:wj2] line += self.ws3 content = line.splitlines(keepends=True) return content
def merge_wdiff3(self, i): """Return content for wdiff by line (3 files)""" (tag, i1, i2, j1, j2, k1, k2, mode, row, bf) = self.opcodes[i] logger.debug( "merge_wdiff3: tag={} a[{}:{}]/b[{}:{}]/c[{}:{}] mode={}, row={}, bf='{}'" .format(tag, i1, i2, j1, j2, k1, k2, mode, row, bf)) line_a = "".join(self.list_a[i1:i2]) line_b = "".join(self.list_b[j1:j2]) line_c = "".join(self.list_c[k1:k2]) if self.isjunk: isjunk = None else: isjunk = lambda x: x in " \t" wseq = SequenceMatcher3(line_a, line_b, line_c, 0, isjunk, True) wopcodes = wseq.get_opcodes() # logger.debug("wdiff3: \nwopcodesc >>>>> {}".format(wopcodes)) line = "" clean_merge = True for tag, wi1, wi2, wj1, wj2, wk1, wk2 in wopcodes: if tag == "E" or tag == "e" or tag == "A": line += line_a[wi1:wi2] elif tag == "C": line += line_c[wk1:wk2] else: # tag == "N" clean_merge = False line += self.ws0 line += line_a[wi1:wi2] line += self.ws1 line += line_b[wj1:wj2] line += self.ws2 line += line_c[wk1:wk2] line += self.ws3 content = line.splitlines(keepends=True) return (clean_merge, content)
def adjust_window(self): """Clamp window scope to have cursor within window and content""" # row, col -> index: first column/line = 0 # winh, winw -> number: first column/line = 1 # conth, contw -> number: first column/line = 1 context_length = 10 self.winh, self.winw = self.stdscr.getmaxyx() if self.update_active and self.active is not None: selected_row = self.get_row(self.actives[self.active]) if self.row >= selected_row - context_length: self.row = selected_row - context_length if self.row <= selected_row - self.winh + context_length: self.row = selected_row - self.winh + context_length # clamp cursor col/row within data if self.row >= self.conth - self.winh: self.row = self.conth - self.winh if self.row < 0: self.row = 0 if self.col >= self.contw - 1: self.col = self.contw - 1 if self.col < 0: self.col = 0 logger.debug( "adjust_window: conth={} contw={} winh={} winw={} row={}, col={}". format(self.conth, self.contw, self.winh, self.winw, self.row, self.col)) return
def command_loop(self): # overridden for TUI by subclassing """Non-interactive driven by MACRO""" logger.debug("command-loop start - macro") while True: # reset flags self.update_textpad = False # TUI self.update_active = False c = self.getch_translated() if c > ord(" ") and c < 127: ch = chr(c) else: ch = " " if c == 0 or ch == "w" or ch == "x": output = self.get_output() write_file(self.file_o, output) break elif ch == "q": # No prompt for CLI break if self.active is not None: # get active chunk # Explicitly select chunk mode if ch in "abcdefg": self.set_mode(self.actives[self.active], ch) elif ch in "1234567": self.set_mode(self.actives[self.active], chr(ord(ch) - ord("1") + ord("a"))) elif ch in "ABCDEFG": self.set_all_mode(ch.lower()) elif ch == "m": self.editor(self.actives[self.active]) elif ch == "M": self.del_editor(self.actives[self.active]) elif ch == "n": self.active_next() elif ch == "p": self.active_prev() elif ch == "t": self.active_home() elif ch == "z": self.active_end() elif ch == "N": self.diff_next() elif ch == "P": self.diff_prev() elif ch == "T": self.diff_home() elif ch == "Z": self.diff_end() else: pass else: pass logger.debug("command-loop") return
def create_template(conf): config_file = os.path.expanduser(conf) if not os.path.exists(config_file): logger.debug("create configuration file: {}".format(conf)) try: with open(config_file, mode="w", buffering=io.DEFAULT_BUFFER_SIZE) as ofp: ofp.write(config_template) except IOError: error_exit("Error in creating configuration file: {}".format(conf)) else: error_exit("Erase {} before 'imediff -t'".format(conf)) return
def merge_diff(self, i): """Return content for diff by line""" (tag, i1, i2, j1, j2, k1, k2, mode, row, bf) = self.opcodes[i] logger.debug( "merge_wdiff: tag={} a[{}:{}]/b[{}:{}]/c[{}:{}] mode={}, row={}, bf='{}'" .format(tag, i1, i2, j1, j2, k1, k2, mode, row, bf)) content = list() content += [self.ls0 % self.file_a] content += self.list_a[i1:i2] if self.diff_mode == 2: content += [self.ls2] content += self.list_b[j1:j2] content += [self.ls3 % self.file_b] else: # self.diff_mode == 3 content += [self.ls1 % self.file_b] content += self.list_b[j1:j2] content += [self.ls2] content += self.list_c[k1:k2] content += [self.ls3 % self.file_c] return content
def new_textpad(self): """Create new curses textpad""" # pre-scan content to get big enough textpad size conth = 0 # content height contw = 0 # content width for i in range(len(self.opcodes)): self.set_row(i, conth) # record textpad row position in chunk tag = self.get_tag(i) content = self.get_content(i) # list() conth += len(content) if tag == "E" or tag == "e": pass else: if len(content) == 0: conth += 1 for line in content: contw = max(contw, console_width(line)) if self.mode: # Add mode column contw += 2 # for the tag indicator + ' ' self.conth = conth self.contw = contw # actual textpad size slightly bigger for safety margin self.textpad = curses.newpad(conth + 1, max(80, contw + 1)) for i in range(len(self.opcodes)): self.textpad_addstr(i, False) if self.active is not None: logger.debug( "gui init: active={} chunk_index={} row={} col={} conth={} contw={}" .format( self.active, self.actives[self.active], self.row, self.col, self.conth, self.contw, )) return
def c_translated(self, c): """translate keys in (' '...'~') according to ~/.imediff""" if c >= ord("A") and c <= ord("Z"): # special handling of upper case letters if c + 32 in list(self.kc.keys()): c = self.kc[c + 32] - 32 logger.debug("c_translated chr = '{}'".format(chr(c))) elif c >= ord(" ") and c <= ord("~"): if c in list(self.kc.keys()): c = self.kc[c] logger.debug("c_translated chr = '{}'".format(chr(c))) else: logger.debug("c_translated num = '{}'".format(c)) return c
def get_opcodes(self): if self.depth == 0: # depth = 0 side = 0 logger.debug( "=== a[{}:{}]/b[{}:{}] === d={:02d} === line[:] ===". format(self.is1, self.is2, self.js1, self.js2, self.depth)) elif self.depth > 0: # depth > 0 if self.depth % 2 == 1: # depth = 1, 3, 5, ... side = +1 logger.debug( "=== a[{}:{}]/b[{}:{}] === d={:02d} === line[:{:02d}] ===" .format(self.is1, self.is2, self.js1, self.js2, self.depth, self.length)) else: # self.depth % 2 == 0: # depth = 2, 4, 6, ... side = -1 logger.debug( "=== a[{}:{}]/b[{}:{}] === d={:02d} === line[-{:02d}:] ===" .format(self.is1, self.is2, self.js1, self.js2, self.depth, self.length)) else: error_exit( "=== a[{}:{}]/b[{}:{}] === d={:02d} should be non-negative". format(self.is1, self.is2, self.js1, self.js2, self.depth)) if side == 0: # self.is1, self.is2, self.js1, self.js2 are known to cover all am = self.a bm = self.b elif side == 1: # left side match am = [] bm = [] for i in range(self.is1, self.is2): am.append(self.a[i][:self.length]) for j in range(self.js1, self.js2): bm.append(self.b[j][:self.length]) else: # side == -1, right side match am = [] bm = [] for i in range(self.is1, self.is2): am.append(self.a[i][-self.length:]) for j in range(self.js1, self.js2): bm.append(self.b[j][-self.length:]) for i in range(self.is1, self.is2): logger.debug("filter a[{}] -> am[{}]='{}'".format( i, i - self.is1, am[i - self.is1])) for j in range(self.js1, self.js2): logger.debug("filter b[{}] -> bm[{}]='{}'".format( j, j - self.js1, bm[j - self.js1])) seq = SequenceMatcher(None, am, bm) match = [] for tag, i1, i2, j1, j2 in seq.get_opcodes(): logger.debug(">>> tag={} === a[{}:{}]/b[{}:{}]".format( tag, i1 + self.is1, i2 + self.is1, j1 + self.js1, j2 + self.js1)) if tag == "equal": for i in range(i1, i2): ip = self.is1 + i jp = self.js1 + j1 + i - i1 if side == 0: # full match on filtered line match.append(("E", ip, ip + 1, jp, jp + 1)) else: # partial match only (F for fuzzy) match.append(("F", ip, ip + 1, jp, jp + 1)) elif i1 == i2 or j1 == j2: # clean insert/delete match.append(("N", i1 + self.is1, i2 + self.is1, j1 + self.js1, j2 + self.js1)) elif (i1 + 1) == i2 and (j1 + 1) == j2: # single line change -> assume fuzzy match without checking match.append(("F", i1 + self.is1, i2 + self.is1, j1 + self.js1, j2 + self.js1)) else: # dig deeper for multi-line changes if side == 0: # full -> left side match.extend( _LineMatcher( a=self.a, b=self.b, is1=i1 + self.is1, is2=i2 + self.is1, js1=j1 + self.js1, js2=j2 + self.js1, length=self.length, depth=self.depth + 1, ).get_opcodes()) elif side == +1: # left side -> right side match.extend( _LineMatcher( a=self.a, b=self.b, is1=i1 + self.is1, is2=i2 + self.is1, js1=j1 + self.js1, js2=j2 + self.js1, length=self.length, depth=self.depth + 1, ).get_opcodes()) elif self.length > self.lenmin: # side == -1 # right side -> left side (shorter) match.extend( _LineMatcher( a=self.a, b=self.b, is1=i1 + self.is1, is2=i2 + self.is1, js1=j1 + self.js1, js2=j2 + self.js1, length=self.length * self.factor // 10, depth=self.depth + 1, ).get_opcodes()) else: # no more shorter, give up as multi-line block change match.append(( "N", i1 + self.is1, i2 + self.is1, j1 + self.js1, j2 + self.js1, )) logger.debug("<<< tag={} === a[{}:{}]/b[{}:{}]".format( tag, i1 + self.is1, i2 + self.is1, j1 + self.js1, j2 + self.js1)) return match
def __init__(self, list_a, list_b, list_c, args, confs): self.diff_mode = args.diff_mode self.default_mode = args.default_mode self.file_a = args.file_a self.file_b = args.file_b self.file_c = args.file_c self.file_o = args.output self.list_a = list_a self.list_b = list_b self.list_c = list_c self.sloppy = args.sloppy self.isjunk = args.isjunk self.linerule = args.linerule self.edit_cmd = args.edit_cmd self.macro = args.macro new_mode = self.default_mode row = None # used only by TUI to match index of opcodes to textpad row bf = None # used only by TUI to store the editor result # set from confs if confs["config"]["confirm_exit"] != "False": self.confirm_exit = True else: self.confirm_exit = False if confs["config"]["confirm_quit"] != "False": self.confirm_quit = True else: self.confirm_quit = False self.ls0 = confs["line_separator"]["ls0"] + " %s\n" self.ls1 = confs["line_separator"]["ls1"] + " %s\n" self.ls2 = confs["line_separator"]["ls2"] + "\n" self.ls3 = confs["line_separator"]["ls3"] + " %s\n" self.ws0 = confs["word_separator"]["ws0"] self.ws1 = confs["word_separator"]["ws1"] self.ws2 = confs["word_separator"]["ws2"] self.ws3 = confs["word_separator"]["ws3"] # command key translation self.kc = dict() # customized key code to original key code self.rkc = dict() # original key chr to customized key chr for cmd_name, key_char in confs["key"].items(): self.kc[ord(key_char[:1])] = ord(cmd_name[-1:]) self.rkc[cmd_name[-1:]] = ord(key_char[:1]) logger.debug( "[key] section: left_side='{}' right_side='{}' : self.krc[{}] = '{}'" .format(cmd_name, key_char, cmd_name[-1:], self.rkc[cmd_name[-1:]])) # parse input data if self.diff_mode == 2: sequence = LineMatcher(list_a, list_b) opcodes = sequence.get_opcodes() k1 = k2 = None # Set initial mode to "a" or "d" self.opcodes = [(tag, i1, i2, j1, j2, k1, k2, "a" if tag == "E" else "d", row, bf) for (tag, i1, i2, j1, j2) in opcodes] self.actives = [ j for j, (tag, i1, i2, j1, j2) in enumerate(opcodes) if tag != "E" ] else: sequence = SequenceMatcher3(list_a, list_b, list_c, 1) opcodes = sequence.get_opcodes() # Set initial mode to "a" or "d" self.opcodes = [(tag, i1, i2, j1, j2, k1, k2, "a" if tag in "Ee" else "d", row, bf) for (tag, i1, i2, j1, j2, k1, k2) in opcodes] self.actives = [ j for j, (tag, i1, i2, j1, j2, k1, k2) in enumerate(opcodes) if tag not in "Ee" ] # self.actives[i] = j -> self.rev_actives[j] = i self.rev_actives = dict() for i, j in enumerate(self.actives): if new_mode != "d": self.set_mode(j, new_mode) self.rev_actives[j] = i if len(self.actives) == 0: self.active = None else: self.active = 0 self.active_old = self.active self.update_active = True self.update_textpad = True # save memory del opcodes # this is not "self.opcodes" del sequence return
def set_all_mode(self, new_mode): for i in range(len(self.opcodes)): logger.debug("set_all_mode: i={} {}".format(i, new_mode)) self.set_mode(i, new_mode) return
def main(): """ Entry point for imediff command Exit value 0 program exits normally after saving data 1 program quits without saving 2 program terminates after an internal error """ # preparation and arguments locale.setlocale(locale.LC_ALL, "") args = initialize_args() if args.template: create_template(args.conf) sys.exit(0) # logging logger.setLevel(logging.DEBUG) if args.debug: # create file handler which logs even debug messages fh = logging.FileHandler("imediff.log") else: # create file handler which doesn't log fh = logging.NullHandler() fh.setLevel(logging.DEBUG) # create console handler with a higher log level ch = logging.StreamHandler() ch.setLevel(logging.ERROR) # create formatter and add it to the handlers formatter = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s") fh.setFormatter(formatter) ch.setFormatter(formatter) # add the handlers to the logger logger.addHandler(fh) logger.addHandler(ch) # configuration confs = initialize_confs(args.conf) editor = "editor" if "EDITOR" in os.environ: editor = os.environ["EDITOR"] if "editor" in confs["config"].keys(): editor = confs["config"]["editor"] args.edit_cmd = shutil.which(editor) if args.edit_cmd is None: args.edit_cmd = "/usr/bin/editor" # safe fall back logger.debug("external editor {} found as {}".format( editor, args.edit_cmd)) # normalize and process non-standard situation if args.version: print(_version) sys.exit(0) if args.diff_mode == 0 and args.non_interactive: tutorial = True if args.diff_mode == 0: args.diff_mode = 3 # Fake input list_a = (_opening + '\n Type "q" to quit this tutorial.').splitlines( keepends=True) list_b = [""] list_c = list_a confs["config"]["confirm_quit"] = "False" confs["config"]["confirm_exit"] = "False" tutorial = True elif args.diff_mode == 2: # diff2 list_a = read_lines(args.file_a) list_b = read_lines(args.file_b) list_c = None tutorial = False elif args.diff_mode == 3: list_a = read_lines(args.file_a) list_b = read_lines(args.file_b) list_c = read_lines(args.file_c) tutorial = False else: error_exit("imediff normally takes 2 or 3 files") # call main routine if not args.non_interactive: display_instance = TextPad(list_a, list_b, list_c, args, confs) display_instance.command_loop(tutorial=tutorial) del display_instance elif tutorial: print(_opening) else: text_instance = TextData(list_a, list_b, list_c, args, confs) text_instance.command_loop() del text_instance sys.exit(0)
def gui_loop(self, stdscr): # for curses TUI (core) # initialize self.stdscr = stdscr color = self.color # shorthand self.winh, self.winw = self.stdscr.getmaxyx() # window size curses.start_color() self.stdscr.clear() self.stdscr.refresh() # set color pair_number as (pair_number, fg, bg) curses.init_pair(1, cc[color["color_a"]], cc["BLACK"]) curses.init_pair(2, cc[color["color_b"]], cc["BLACK"]) curses.init_pair(3, cc[color["color_c"]], cc["BLACK"]) curses.init_pair(4, cc[color["color_d"]], cc["BLACK"]) curses.init_pair(5, cc[color["color_e"]], cc["BLACK"]) curses.init_pair(6, cc[color["color_f"]], cc["BLACK"]) # # +6: active cc self.active_color = 6 curses.init_pair(7, cc["WHITE"], cc[color["color_a"]]) curses.init_pair(8, cc["WHITE"], cc[color["color_b"]]) curses.init_pair(9, cc["WHITE"], cc[color["color_c"]]) curses.init_pair(10, cc["WHITE"], cc[color["color_d"]]) curses.init_pair(11, cc["WHITE"], cc[color["color_e"]]) curses.init_pair(12, cc["WHITE"], cc[color["color_f"]]) # # +12: deleted cc self.deleted_color = 12 curses.init_pair(13, cc[color["color_a"]], cc["WHITE"]) curses.init_pair(14, cc[color["color_b"]], cc["WHITE"]) curses.init_pair(15, cc[color["color_c"]], cc["WHITE"]) curses.init_pair(16, cc[color["color_d"]], cc["WHITE"]) curses.init_pair(17, cc[color["color_e"]], cc["WHITE"]) curses.init_pair(18, cc[color["color_f"]], cc["WHITE"]) # # +6+12 curses.init_pair(19, cc["BLACK"], cc[color["color_a"]]) curses.init_pair(20, cc["BLACK"], cc[color["color_b"]]) curses.init_pair(21, cc["BLACK"], cc[color["color_c"]]) curses.init_pair(22, cc["BLACK"], cc[color["color_d"]]) curses.init_pair(23, cc["BLACK"], cc[color["color_e"]]) curses.init_pair(24, cc["BLACK"], cc[color["color_f"]]) # if curses.has_colors() == False: self.mono = True if self.mono: self.mode = True self.color_a = "WHITE" self.color_b = "WHITE" self.color_c = "WHITE" self.color_d = "WHITE" self.color_e = "WHITE" self.color_f = "WHITE" else: if self.diff_mode == 2: self.color_a = color["color_a"] self.color_b = color["color_b"] # self.color_c = color['color_c'] # never used self.color_d = color["color_d"] self.color_e = color["color_e"] self.color_f = color["color_f"] else: self.color_a = color["color_a"] self.color_b = color["color_b"] self.color_c = color["color_c"] self.color_d = color["color_d"] self.color_e = color["color_e"] self.color_f = color["color_f"] # display parameters self.col = 0 # the column coordinate of textpad (left most=0) self.row = 0 # the row coordinate of textpad (top most=0) self.update_textpad = True # update textpad content while True: if self.active is not None: logger.debug( "command loop: active = {} active_index = {} row = {} col = {}" .format(self.active, self.actives[self.active], self.row, self.col)) else: logger.debug( "command loop: active = ***None*** row = {} col = {}". format(self.row, self.col)) curses.curs_set(0) if self.update_textpad: self.new_textpad() # clear to remove garbage outside of textpad self.winh, self.winw = self.stdscr.getmaxyx() self.adjust_window() for icol in range(self.contw - self.col, self.winw): self.stdscr.vline(0, icol, " ", self.winh) ##self.stdscr.vline(0, self.contw - self.col, '@', self.winh) ##self.stdscr.vline(0, self.winw-1, '*', self.winh) # clear rows downward to remove garbage characters for irow in range(self.conth - self.row, self.winh): self.stdscr.hline(irow, 0, " ", self.winw) ##if (self.conth - self.row) <= self.winh -1 and (self.conth - self.row) >= 0: ## self.stdscr.hline(self.conth - self.row , 0, '@', self.winw) if self.update_textpad or self.update_active: self.highlight() self.textpad.refresh(self.row, self.col, 0, 0, self.winh - 1, self.winw - 1) if self.active is not None: row = self.get_row(self.actives[self.active]) - self.row if row >= 0 and row < self.winh: self.stdscr.move(row, 0) curses.curs_set(1) else: curses.curs_set(0) self.stdscr.refresh() # reset flags self.update_textpad = False self.update_active = False if self.tutorial: c = ord("H") self.tutorial = False else: c = self.getch_translated() ch = chr(c) if ch == "w" or ch == "x" or c == curses.KEY_EXIT or c == curses.KEY_SAVE: if self.sloppy or (not self.sloppy and self.get_unresolved_count() == 0): if not self.confirm_exit or self.popup( _("Do you 'save and exit'? (Press '{y:c}' to exit)" ).format(y=self.rkc["y"])): output = self.get_output() write_file(self.file_o, output) break else: self.popup( _("Can't 'save and exit' due to the non-clean merge. (Press '{y:c}' to continue)" + "\n\n" + _nonclean).format(y=self.rkc["y"])) elif ch == "q": if not self.confirm_exit or self.popup( _("Do you 'quit without saving'? (Press '{y:c}' to quit)" ).format(y=self.rkc["y"])): self.opcodes = [] error_exit("Quit without saving by the user request\n") elif ch == "h" or c == curses.KEY_HELP: # Show help screen self.popup(self.helptext()) elif ch == "H": # Show tutorial screen self.popup(_tutorial) elif ch == "s" or ch == "?": # Show location if len(self.actives) == 0: self.popup( _stattext0.format(row=self.row, conth=self.conth, col=self.col)) else: self.popup( _stattext1.format( active=self.active, total=len(self.actives), unresolved=self.get_unresolved_count(), row=self.row, conth=self.conth, col=self.col, )) # Moves in document elif c == curses.KEY_SR or c == curses.KEY_UP or ch == "k": self.row -= 1 elif c == curses.KEY_SF or c == curses.KEY_DOWN or ch == "j": self.row += 1 elif c == curses.KEY_LEFT: self.col -= 8 elif c == curses.KEY_RIGHT: self.col += 8 elif c == curses.KEY_PPAGE: self.row -= self.winh elif c == curses.KEY_NPAGE: self.row += self.winh # Terminal resize signal elif c == curses.KEY_RESIZE: self.winh, self.winw = self.stdscr.getmaxyx() else: pass # Following key-command updates TextPad if self.active is not None: # get active chunk # Explicitly select chunk mode if ch in "abdef": self.set_mode(self.actives[self.active], ch) elif ch in "12456": self.set_mode(self.actives[self.active], chr(ord(ch) - ord("1") + ord("a"))) elif ch in "ABDEF": self.set_all_mode(ch.lower()) elif ch in "cg" and self.diff_mode == 3: self.set_mode(self.actives[self.active], ch) elif ch in "37" and self.diff_mode == 3: self.set_mode(self.actives[self.active], chr(ord(ch) - ord("1") + ord("a"))) elif ch in "CG" and self.diff_mode == 3: self.set_all_mode(ch.lower()) elif c == 10 or c == curses.KEY_COMMAND: mode = self.get_mode(self.actives[self.active]) if mode == "a": self.set_mode(self.actives[self.active], "b") elif mode == "b" and self.diff_mode == 2: self.set_mode(self.actives[self.active], "d") elif mode == "b" and self.diff_mode == 3: self.set_mode(self.actives[self.active], "c") elif mode == "c": self.set_mode(self.actives[self.active], "d") elif (mode == "d" and self.get_bf( self.actives[self.active]) is not None): self.set_mode(self.actives[self.active], "e") elif mode == "d": self.set_mode(self.actives[self.active], "f") elif mode == "e": self.set_mode(self.actives[self.active], "f") else: # f self.set_mode(self.actives[self.active], "a") elif ch == "m": self.editor(self.actives[self.active]) elif ch == "M" and mode == "e": self.del_editor(self.actives[self.active]) elif ch == "n" or c == curses.KEY_NEXT or ch == " ": self.active_next() elif ch == "p" or c == curses.KEY_PREVIOUS or c == curses.KEY_BACKSPACE: self.active_prev() elif ch == "t" or c == curses.KEY_HOME: self.active_home() elif ch == "z" or c == curses.KEY_END: self.active_end() elif ch == "N" or ch == "\t": self.diff_next() elif ch == "P" or c == curses.KEY_BTAB: self.diff_prev() elif ch == "T": self.diff_home() elif ch == "Z": self.diff_end() else: pass logger.debug("command-loop") return
def get_opcodes(self): """Return list of 7-tuples describing how a, b, c matches. Each tuple is of the form (tag, i1, i2, j1, j2, k1, k2). The first tuple has i1 == j1 == k1 == 0, and remaining tuples have i1 == the i2 from the tuple preceding it, and likewise for j1 == the previous j2, and likewise for k1 == the previous k2. The tags are strings, with these meanings: 'E': a == b == c 'e': a != b != c == A 'A': c == b --> a, change in a 'C': a == b --> c, change in c 'N': b-->a, b-->c, conflicting changes >>> a = "qabxcdsdgp" >>> b = "abycdfsdkg" >>> c = "abycdfzcpgp" >>> s = SequenceMatcher3(a, b, c) >>> for tag, i1, i2, j1, j2, k1, k2 in s.get_opcodes(): ... print(("%s a[%d:%d] (%s) / b[%d:%d] (%s) / c[%d:%d] (%s)" % ... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2], k1, k2, c[k1:k2]))) ... A a[0:1] (q) / b[0:0] () / c[0:0] () E a[1:3] (ab) / b[0:2] (ab) / c[0:2] (ab) A a[3:4] (x) / b[2:3] (y) / c[2:3] (y) E a[4:6] (cd) / b[3:5] (cd) / c[3:5] (cd) A a[6:6] () / b[5:6] (f) / c[5:6] (f) N a[6:8] (sd) / b[6:9] (sdk) / c[6:9] (zcp) E a[8:9] (g) / b[9:10] (g) / c[9:10] (g) e a[9:10] (p) / b[10:10] () / c[10:11] (p) """ a = self.a b = self.b c = self.c matcher = self.matcher if matcher == 0: opcodes_ba = SequenceMatcher(self.isjunk, b, a).get_opcodes() opcodes_bc = SequenceMatcher(self.isjunk, b, c).get_opcodes() tag_equal = "equal" else: # matcher == 1 opcodes_ba = LineMatcher(b, a, self.linerule).get_opcodes() opcodes_bc = LineMatcher(b, c, self.linerule).get_opcodes() tag_equal = "E" n_ba = 0 # walking index for opcodes_ba n_bc = 0 # walking index for opcodes_bc len_ba = len(opcodes_ba) len_bc = len(opcodes_bc) logger.debug("diff3lib: matcher={} tag={} len_ba={} len_bc={}".format( matcher, tag_equal, len_ba, len_bc)) il = jl = kl = 0 # range lower end for b, a, c (next in next round) ih = jh = kh = 0 # range high end for b, a, c (next in next round) answer = list() tag = "" while n_ba < len_ba or n_bc < len_bc: # get a chunk data if n_ba < len_ba: (tag_ba, il_ba, ih_ba, jl_ba, jh_ba) = opcodes_ba[n_ba] logger.debug("diff3lib: NORMAL n_bc={} < len_bc={}".format( n_bc, len_bc)) elif len_ba == 0: (tag_ba, il_ba, ih_ba, jl_ba, jh_ba) = (tag_equal, 0, 0, 0, 0) logger.debug("diff3lib: UNDERRUN n_ba={} len_ba={}".format( n_ba, len_ba)) else: (tag_ba, il_ba, ih_ba, jl_ba, jh_ba) = opcodes_ba[len_ba - 1] (tag_ba, il_ba, ih_ba, jl_ba, jh_ba) = ( tag_equal, ih_ba, ih_ba, jh_ba, jh_ba, ) logger.debug("diff3lib: OVERRUN n_ba={} len_ba={}".format( n_ba, len_ba)) if n_bc < len_bc: (tag_bc, il_bc, ih_bc, kl_bc, kh_bc) = opcodes_bc[n_bc] logger.debug("diff3lib: NORMAL n_bc={} < len_bc={}".format( n_bc, len_bc)) elif len_bc == 0: (tag_bc, il_bc, ih_bc, kl_bc, kh_bc) = (tag_equal, 0, 0, 0, 0) logger.debug("diff3lib: UNDERRUN n_bc={} len_bc={}".format( n_bc, len_bc)) else: (tag_bc, il_bc, ih_bc, kl_bc, kh_bc) = opcodes_bc[len_bc - 1] (tag_bc, il_bc, ih_bc, kl_bc, kh_bc) = ( tag_equal, ih_bc, ih_bc, kh_bc, kh_bc, ) logger.debug("diff3lib: OVERRUN n_bc={} len_bc={}".format( n_bc, len_bc)) # get tag for this set of opcodes if high range value is available if tag == "N": pass # All undecided comes in as tag == "N", otherwise tag == "" elif tag_ba == tag_equal and tag_bc == tag_equal: tag = "E" elif tag_ba != tag_equal and tag_bc == tag_equal: tag = "A" elif tag_ba == tag_equal and tag_bc != tag_equal: tag = "C" elif tag_ba != tag_equal and tag_bc != tag_equal: tag = "N" if ih_ba == ih_bc: n_ba += 1 n_bc += 1 ih = ih_ba # == ih_bc jh = jh_ba kh = kh_bc elif ih_ba > ih_bc: n_bc += 1 ih = ih_bc kh = kh_bc if tag_ba == tag_equal: jh = jh_ba - (ih_ba - ih_bc) elif jh_ba == jl_ba: jh = jh_ba else: jh = None # undecided tag = "N" if ih_ba == ih_bc: kh = kh_bc elif ih_ba < ih_bc: n_ba += 1 ih = ih_ba jh = jh_ba if tag_bc == tag_equal: kh = kh_bc - (ih_bc - ih_ba) elif kh_bc == kl_bc: kh = kh_bc else: kh = None # undecided tag = "N" if jh is None: logger.debug( "diff3lib: UNDECIDED_ba tag={}, jl={}, jh=None, il={}, ih={}, kl={}, kh={}" .format(tag, jl, il, ih, kl, kh)) elif kh is None: logger.debug( "diff3lib: UNDECIDED_bc tag={}, jl={}, jh={}, il={}, ih={}, kl={}, kh=None" .format(tag, jl, jh, il, ih, kl)) else: if tag == "N": if a[jl:jh] == c[kl:kh]: tag = "e" answer.append((tag, jl, jh, il, ih, kl, kh)) logger.debug( "diff3lib: APPEND tag={}, jl={}, jh={}, il={}, ih={}, kl={}, kh={}" .format(tag, jl, jh, il, ih, kl, kh)) il = ih jl = jh kl = kh tag = "" self.opcodes = answer return answer