예제 #1
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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
예제 #2
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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)
예제 #3
0
 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
예제 #4
0
파일: cli.py 프로젝트: osamuaoki/imediff
    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
예제 #5
0
파일: config.py 프로젝트: osamuaoki/imediff
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
예제 #6
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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
예제 #7
0
 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
예제 #8
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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
예제 #9
0
 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
예제 #10
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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
예제 #11
0
파일: cli.py 프로젝트: osamuaoki/imediff
 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
예제 #12
0
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)
예제 #13
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
예제 #14
0
    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