# ============================================================================== # RSfileMerge.nm - Tony Balinski # # This file contains the following functions: # fileMerge_extract_diffinfo - used internally # fileMerge_ask_for_diff_line - used internally # fileMerge_get_diff_data - used internally # fileMerge_is_lines_info - used internally # fileMerge_get_lines_range - used internally # fileMerge_get_lines_info - used internally # fileMerge_RS_recolor - used internally # fileMerge_RS_color - used internally # fileMerge_display_file_merge - used internally # fileMerge_display_file_mergeREV - used internally # fileMerge_diff_details - used internally # fileMerge_rework_display - used internally # fileMerge_rework_changes - post-processes fileMerge_from_diff # fileMerge_CopyLeftRight - used internally # fileMerge_from_diff # ============================================================================== # These macros are designed to be used to post-process the output of the diff(1) # command (using its simplest form). The diff output should be in the current # window, preceded by the diff command itself; if you use diff with # directories, this is the sort of output you should get anyway. Otherwise, the # easiest way to perform the diff is to type the command line directly into a # new NEdit window and hit keypad-enter (or Shell->Execute Command Line). # ============================================================================== NEDIT_require_macro_file("extensions.nm") NEDIT_require_macro_file("array_utils.nm") rsA = "Added" rsD = "Deleted" rsO = "Changed (old)" rsN = "Changed (new)" $fileMergeRSModNames = make_arraylist(rsA, rsD, rsO, rsN) rsS = "Diff spec" rsI = "Ignored" rsH = "Current diff" rsF = "Diff output" $fileMergeRSDiffParts = make_arraylist(rsS, rsI, rsH, rsF) $fileMergeRSNames = $fileMergeRSModNames + \ shift_arraylist($fileMergeRSModNames[], \ $fileMergeRSDiffParts) $fileMergeRSColors = make_array(rsA, "green", # added \ rsD, "red", # deleted \ rsO, "orange", # changed: old stuff \ rsN, "cyan", # changed: new stuff \ rsS, "cyan", # diff specification heading \ rsI, "gray", # invalid/ignored "diff" lines \ rsH, "orange", # current diff "header" line \ rsF, "white") # diff output to handle # ============================================================================== # Indices to the info "structure" returned by fileMerge_extract_diffinfo(). # ============================================================================== $fileMergeI_diffnum = 0 $fileMergeI_diffpos = 1 $fileMergeI_startpos = 2 $fileMergeI_endpos = 3 $fileMergeI_file1 = 4 $fileMergeI_file2 = 5 $fileMergeI_diffline = 6 $fileMergeI_operation = 7 # ============================================================================== # Global patterns (with initialisation code). # ============================================================================== $fileMerge_diffpat = "" $fileMerge_linepat = "" if ($fileMerge_diffpat == "") { fname = "((?:\\\\.|\\S)+)" # captured filename with \escs diffpat = "^diff\\s+" # start of line must be diff diffpat = diffpat "(-\\S+\\s+)*" # \1 = options to diff diffpat = diffpat fname "\\s+" fname # \2 = file1, \3 = file2 diffpat = diffpat "\\s*$" # trailing spaces $fileMerge_diffpat = diffpat } if ($fileMerge_linepat == "") { linepat = "^(" linepat = linepat "\\d+(,\\d+)?[acd]\\d+(,\\d+)?" # diff region linepat = linepat "|\\< .*" # take out line linepat = linepat "|-+" # ignore linepat = linepat "|\\> .*" # insert line linepat = linepat ")$" # end line $fileMerge_linepat = linepat } # ============================================================================== # Root of temporary file name to use for analysing change sections. # (See fileMerge_rework_changes().) # Change this as required (eg to use directory $HOME or /tmp etc.) # ============================================================================== $fileMerge_rework_change_file_root = $NEDIT_START_DIR "/.~diff_change_" # ============================================================================== # Maximum lengths (stored as strings of spaces) of file names retrieved for # columnar presentation in dialog. # ============================================================================== $fileMerge_file1sp = "" $fileMerge_file2sp = "" $fileMerge_num = "" # ============================================================================== # fileMerge_read_file(file): evaluates the full path for the file name given, # using the current window's path for relative paths. It then tries to # read the file there. Returns an array, which is empty on failure, and # contains the content of the file at index zero otherwise. # The reason for this function is that the read/write_file() NEdit # built-in functions do not take the window's current path into account. # Rather, relative file names are evaluated from the directory from which # NEdit was invoked. # ============================================================================== define fileMerge_read_file { file = $1 if (substring(file, 0, 1) != "/") file = $file_path file content = read_file(file) if (!$read_status) return $empty_array array[0] = content return array } # ============================================================================== # fileMerge_write_file(buffer, file): evaluates the full path for the file name given, # using the current window's path for relative paths. It then tries to # write the buffer contents to the file there. Returns a true (non-zero) # value if successful, false (zero) otherwise. # The reason for this function is that the read/write_file() NEdit # built-in functions do not take the window's current path into account. # Rather, relative file names are evaluated from the directory from which # NEdit was invoked. # ============================================================================== define fileMerge_write_file { buffer = $1 file = $2 if (substring(file, 0, 1) != "/") file = $file_path file return write_file(buffer, file) } # ============================================================================== # fileMerge_hexVal(hexStr): convert hexStr as hexadecimal string to a number. # ============================================================================== define fileMerge_hexVal { val = 0 hexStr = $1 for (i = 0; i < length(hexStr); i++) { ch = substring(hexStr, i, i + 1) v = search_string("0123456789abcdef", ch, 0, "literal") if (v == -1) break val = 16 * val + v } return val } # ============================================================================== # fileMerge_mapRSbg_ask(): asks whether a bright or dark background should be # assumed. It then recolors existing known rangesets appropriately. # ============================================================================== define fileMerge_mapRSbg_ask { # do we have bright or dark? if (background_is_dark("ask")) $fileMerge_mapRSbg["bg"] = "dark" else $fileMerge_mapRSbg["bg"] = "bright" s = "Recoloring:" cols = $fileMergeRSColors for (rsn in $fileMergeRSColors) { newcol = fileMerge_mapRSbg($fileMergeRSColors[rsn]) r = fileMerge_RS_recolor(rsn, newcol, -1) if (r && rangeset_info(r)["count"] == 0) rangeset_destroy(r) else if (r) { ncoln = newcol " (" fileMerge_nameRSbg(newcol) ")" s = s "\n " rsn ": color " $fileMergeRSColors[rsn] " -> " ncoln } } dialog(s) } # ============================================================================== # fileMerge_mapRSbg(color): uses get_style_at_pos() to try to determine whether # the background is "light" or "dark". It then uses the color string to # decide on a correct value. # ============================================================================== define fileMerge_mapRSbg { $fileMerge_mapRSbg[""] = "" # do we have bright or dark? if (background_is_dark()) $fileMerge_mapRSbg["bg"] = "dark" else $fileMerge_mapRSbg["bg"] = "bright" if ($fileMerge_mapRSbg["bg"] == "dark") { # dark background if ($1 == "red") return "#7f0000" else if ($1 == "green") return "#006000" else if ($1 == "orange") return "#7f4f00" else if ($1 == "cyan") return "#006090" else if ($1 == "gray") return "#7f7f7f" else if ($1 == "white") return "#000000" } # default value is the color passed in return $1 } # ============================================================================== # fileMerge_nameRSbg(color): maps the given color name to the "original" color. # ============================================================================== define fileMerge_nameRSbg { if ($1 == "#7f0000") return "dark red" else if ($1 == "#006000") return "dark green" else if ($1 == "#7f4f00") return "dark orange" else if ($1 == "#006090") return "dark blue" else if ($1 == "#7f7f7f") return "gray" else if ($1 == "#000000") return "black" # default value is the color passed in return $1 } # ============================================================================== # fileMerge_extract_diffinfo(difftext): scans difftext looking for lines of the # form "diff -options file1 file2". Returns the information in an array of # six entries: # [0] diff_num - index number of info string # [1] diffpos - position of start of diff command # [2] startpos - position of start of diff information # [3] endpos - position of end of diff information # [4] file1 - name of file 1 # [5] file2 - name of file 2 # [6] diff line - what was chosen # Fails with an empty array. # ============================================================================== define fileMerge_extract_diffinfo { difftext = split($1, "\n") diffinfo = $empty_array diff_num = 0 diffline = "" diff_file1 = "" diff_file2 = "" $fileMerge_file1sp = "" $fileMerge_file2sp = "" $fileMerge_num = "" diffpat = $fileMerge_diffpat linepat = $fileMerge_linepat beg = 0 end = 0 in_diff = 0 info = $empty_array ################################ # # for colorful debugging # z = rangeset_create() # rangeset_set_color(z, "cyan") # zz = 1 # zzz = $cursor ################################ for (lineNo = 0; lineNo < difftext[]; lineNo++) { # pick up the line line = difftext[lineNo] ################################ # # for colorful debugging # set_cursor_pos(zzz) # rangeset_destroy(z) # z = rangeset_create() # rangeset_set_color(z, "cyan") # rangeset_add(z, beg, beg + length(line)) # set_cursor_pos(beg) # if (zz == 1) zz = dialog(line, "OK", "Skip") # set_cursor_pos(zzz) ################################ end = beg + length(line) if (!in_diff && search_string(line, diffpat, 0, "regex") != -1) { # we have a diff spec diffpos = beg diffstart = end + 1 diffline = line in_diff = 1 # search to end of this diff section files = replace_in_string(line, diffpat, "\\2\n\\3", "regex") afiles = split(files, "\n") diff_file1 = unquote(afiles[0]) diff_file2 = unquote(afiles[1]) if (length(diff_file1) > length($fileMerge_file1sp)) $fileMerge_file1sp = replace_in_string(diff_file1, ".", " ", "regex") if (length(diff_file2) > length($fileMerge_file2sp)) $fileMerge_file2sp = replace_in_string(diff_file2, ".", " ", "regex") } else if (in_diff && search_string(line, linepat, 0, "regex") != -1) { # we have the content of a diff - just carry on in_diff = 1 # search to end of this diff section } else if (in_diff && search_string(line, diffpat, 0, "regex") != -1) { # we have a diff spec but were looking for the end of the previous # that means that beg is the end marker of previous difference section info = make_arraylist(diff_num, \ diffpos, \ diffstart, \ beg, \ diff_file1, \ diff_file2, \ diffline) diffinfo[diff_num] = info diff_num++ # we start the content of a new diff diffpos = beg diffstart = end + 1 diffline = line in_diff = 1 # search to end of this diff section files = replace_in_string(line, diffpat, "\\2\n\\3", "regex") afiles = split(files, "\n") diff_file1 = unquote(afiles[0]) diff_file2 = unquote(afiles[1]) if (length(diff_file1) > length($fileMerge_file1sp)) $fileMerge_file1sp = replace_in_string(diff_file1, ".", " ", "regex") if (length(diff_file2) > length($fileMerge_file2sp)) $fileMerge_file2sp = replace_in_string(diff_file2, ".", " ", "regex") } else if (in_diff) { # we have the end of the previous diff # that means that beg is the end marker of previous difference section if (beg > diffstart) # make sure we have something! { info = make_arraylist(diff_num, \ diffpos, \ diffstart, \ beg, \ diff_file1, \ diff_file2, \ diffline) diffinfo[diff_num] = info diff_num++ # start looking for "diff" again } in_diff = 0 # skip to next diff section } # end of loop: iterate beg = end + 1 } $fileMerge_num = replace_in_string(diff_num, ".", " ", "regex") return diffinfo } # ============================================================================== # fileMerge_ask_for_diff_line(diffinfo): Uses information from diffinfo to # present lines of the form "diff -options file1 file2" to the user in a # list_dialog(). The user chooses an entry, and this function returns a 7 # element array in the format: # [0] diff_num - index number of info string # [1] diffpos - position of start of diff command # [2] startpos - position of start of diff information # [3] endpos - position of end of diff information # [4] file1 - name of file 1 # [5] file2 - name of file 2 # [6] diff line - what was chosen # [7] diff choice - "Analyse", "Locate", "Forget", "Copy" # operation # Fails with an empty array. # ============================================================================== define fileMerge_ask_for_diff_line { diffinfo = $1 if (diffinfo[] == 0) return $empty_array # nothing found... difflines = "" for (diff_num = 0; diff_num in diffinfo; diff_num++) { info = diffinfo[diff_num] file1 = info[$fileMergeI_file1] file1 = substring(file1 $fileMerge_file1sp, 0, length($fileMerge_file1sp)) file2 = info[$fileMergeI_file2] file2 = substring(file2 $fileMerge_file2sp, 0, length($fileMerge_file2sp)) # add 1 to line number for display num = info[$fileMergeI_diffnum] + 1 num = substring($fileMerge_num num, length(num)) difflines = difflines num ": " file1 " " file2 "\n" } which = list_dialog("Analyse which diff?", difflines, \ "Merge", "Locate", "Forget", "Copy", "Cancel") if ($list_dialog_button < 1 || $list_dialog_button > 4 || which == "") return $empty_array # user selected nothing if ($list_dialog_button == 1) button = "Analyse" else if ($list_dialog_button == 2) button = "Locate" else if ($list_dialog_button == 3) button = "Forget" else if ($list_dialog_button == 4) button = "Copy" else return $empty_array # remove 1 from chosen line number to retrieve index i = replace_in_string(which, " *([0-9]+):.*", "\\1", "regex") i-- info = diffinfo[i] info[$fileMergeI_operation] = button return info } # ============================================================================== # fileMerge_ask_for_mdiff_line(diffinfo): Uses information from diffinfo to # present lines of the form "diff -options file1 file2" to the user in a # list_dialog(). The user chooses an entry, and this function returns a 7 # element array in the format: # [0] diff_num - index number of info string # [1] diffpos - position of start of diff command # [2] startpos - position of start of diff information # [3] endpos - position of end of diff information # [4] file1 - name of file 1 # [5] file2 - name of file 2 # [6] diff line - what was chosen # [7] diff choice - "Analyse", "Locate", "Forget", "Copy" # operation # Fails with an empty array. # ============================================================================== define fileMerge_ask_for_mdiff_line { diffinfo = $1 if (diffinfo[] == 0) return $empty_array # nothing found... difflines = "" for (diff_num = 0; diff_num in diffinfo; diff_num++) { info = diffinfo[diff_num] file1 = info[$fileMergeI_file1] file1 = substring(file1 $fileMerge_file1sp, 0, length($fileMerge_file1sp)) file2 = info[$fileMergeI_file2] file2 = substring(file2 $fileMerge_file2sp, 0, length($fileMerge_file2sp)) # add 1 to line number for display num = info[$fileMergeI_diffnum] + 1 num = substring($fileMerge_num num, length(num)) difflines = difflines num ": " file1 " " file2 "\n" } which = list_multisel_dialog("Analyse which diff?", difflines, \ "Merge", "Locate", "Forget", "Copy", "Cancel") #which = list_dialog("Analyse which diff?", difflines, \ # "Merge", "Locate", "Forget", "Copy", "Cancel") if ($list_dialog_button < 1 || $list_dialog_button > 4 || which == "") return $empty_array # user selected nothing if ($list_dialog_button == 1) button = "Analyse" else if ($list_dialog_button == 2) button = "Locate" else if ($list_dialog_button == 3) button = "Forget" else if ($list_dialog_button == 4) button = "Copy" else return $empty_array # remove 1 from chosen line number to retrieve index ii = replace_in_string(which, " *([0-9]+):.*", "\\1", "regex") inds = split(ii, "\n") info = $empty_array for (n = 0; n in inds; n++) { i = inds[n] - 1 info[n] = diffinfo[i] info[n][$fileMergeI_operation] = button } return info } # ============================================================================== # fileMerge_get_diff_data(difftext, info): extracts diff data given difftext # and an information string as returned by fileMerge_ask_for_diff_line(). # Fails with an empty string. # ============================================================================== define fileMerge_get_diff_data { if ($n_args != 2) return "" difftext = $1 info = $2 beg = info[$fileMergeI_startpos] end = info[$fileMergeI_endpos] if (beg == "" || end == "") return "" p1 = beg + 0 p2 = end + 0 if (p1 != beg || p2 != end) return "" return substring(difftext, beg, end) } # ============================================================================== # fileMerge_is_lines_info(line): returns true if the line specifies ranges of # lines to add/change/delete. # ============================================================================== define fileMerge_is_lines_info { linepat = "^[0-9]+(,[0-9]+)?[acd][0-9]+(,[0-9]+)?\n" if (search_string($1, linepat, 0, "regex") == 0) return 1 return 0 } # ============================================================================== # fileMerge_get_lines_range(range): if the range string looks like N, returns # "N\nN\n" (doubling up the only value); if it looks like "N,M", returns # "N\nM\n". # ============================================================================== define fileMerge_get_lines_range { res = split($1, ",") if (res[] == 1) return make_arraylist($1, $1) else return res } # ============================================================================== # fileMerge_get_lines_info(line): returns an information string about the # ranges of lines to add/change/delete as held in the line passed. The # information is returned in a string of format: # operation - a:add, d:delete, c: change # file1_from - from line for first file # file1_to - to line for first file # file2_from - from line for second file # file2_to - to line for second file # ============================================================================== define fileMerge_get_lines_info { line = substring($1, 0, -1) # remove "\n" pos = search_string(line, "[adc]", 0, "regex") pt1 = fileMerge_get_lines_range(substring(line, 0, pos)) type = substring(line, pos, pos + 1) pt2 = fileMerge_get_lines_range(substring(line, pos + 1)) return make_arraylist(type, pt1[0], pt1[1], pt2[0], pt2[1]) } # ============================================================================== # fileMerge_RS_recolor(RS, color, check): if check is positive and the rangeset # is defined, does nothing; otherwise, colors the rangeset with the # desired color, creating it if check is non-negative. If no color is # given, just makes sure the rangeset exists, creating it if necessary. # The modify response is set to break. # ============================================================================== define fileMerge_RS_recolor { id = 0 check = $n_args < 3 # check is 1 by default if (!check) check = $3 ids = rangeset_get_by_name($1) if (ids[] > 1) dialog("Rangeset \""$1"\" defined more than once") if (ids[] > 0) id = ids[0] if (check > 0 && id) return id if (!id && check >= 0) { id = rangeset_create() rangeset_set_name(id, $1) rangeset_set_mode(id, "break") } if (id) { if ($n_args >= 2) rangeset_set_color(id, fileMerge_mapRSbg($2)) } return id } # ============================================================================== # fileMerge_RS_color(RS, color): defines the new rangeset with the desired # color. The modify response is set to break. # ============================================================================== define fileMerge_RS_color { rangeset_destroy(rangeset_get_by_name($1)) return fileMerge_RS_recolor($1, $2, 0) # no check - creates RS } # ============================================================================== # fileMerge_RS_makeModified(): uses $fileMergeRSModNames as names of range sets # to add to the "Modified" rangeset. # ============================================================================== define fileMerge_RS_makeModified { # Create a rangeset which is the merge of the difference ranges found rM = fileMerge_RS_color("Modified", "") for (i in $fileMergeRSModNames) { L = $fileMergeRSModNames[i] ids = rangeset_get_by_name(L) if (ids[] == 0) continue if (ids[] > 1) dialog("Rangeset \""L"\" defined more than once") r = ids[0] if (r != 0 && r != rM) { count = rangeset_info(r)["count"] if (count) rangeset_add(rM, r) else { # we don't need this RS anymore rangeset_destroy(r) } } } } # ============================================================================== # fileMerge_display_file_mergeFWD(diffdata, info [, lang]): opens the first file # of the diff and integrates info about the second file, using the # difference data in diffdata. This is done backwards. A default language # mode lang can be provided. # ============================================================================== define fileMerge_display_file_mergeFWD { rsA = $fileMergeRSModNames[0] rsD = $fileMergeRSModNames[1] rsO = $fileMergeRSModNames[2] rsN = $fileMergeRSModNames[3] if ($n_args < 2 || $n_args > 3) return "" diffdata = $1 info = $2 lang = "" if ($n_args == 3) lang = $3 pos = length(diffdata) if (substring(diffdata, pos - 1, pos) == "\n") pos-- array = fileMerge_read_file(info[$fileMergeI_file1]) if (!array[]) return "" file1data = array[0] # create a new window, make it active new("tab") # can't give it a title ("Merged from: " info[$fileMergeI_diffline]) focus_window("last") raise_window() # set the language mode (if there is one to set) if (lang != "" && ($language_mode == "" || $language_mode == "Plain")) set_language_mode(lang) window_name = $file_path $file_name replace_range($cursor, $cursor, file1data) set_cursor_pos($cursor + length(file1data)) prev = pos - 1 inserted = "" deleted = "" # create the differently colored rangesets for the changed lines cols = $fileMergeRSColors rA = fileMerge_RS_color(rsA, cols[rsA]) # add rD = fileMerge_RS_color(rsD, cols[rsD]) # delete rO = fileMerge_RS_color(rsO, cols[rsO]) # change: old stuff rN = fileMerge_RS_color(rsN, cols[rsN]) # change: new stuff # read the difference data from the end, backwards while (prev >= 0) { prev = search_string(diffdata, "\n", pos - 1, "backward") line = substring(diffdata, prev + 1, pos + 1) # include ending "\n" if (substring(line, 0, 2) == "> ") inserted = substring(line, 2) inserted else if (substring(line, 0, 2) == "< ") deleted = substring(line, 2) deleted else if (fileMerge_is_lines_info(line)) { lines = fileMerge_get_lines_info(line) operation = lines[0] # a:add, d:delete, c: change file1_from = lines[1] # from line for first file file1_to = lines[2] # to line for first file file2_from = lines[3] # from line for second file file2_to = lines[4] # to line for second file rs = "" # select line, put cursor at front goto_line_number(file1_from, 0) # deal with removed data opC = (operation == "c") if (operation == "d" || opC) { rs = (opC * rO) + (!opC * rD) ins = $cursor goto_line_number(file1_to + 1, 0) rangeset_add(rs, ins, $cursor) } # deal with added data if (operation == "a" || opC) { rs = (opC * rN) + (!opC * rA) if (!opC) { rs = rA if (file1_to > 0) # file1_to == 0 is a special case process_down() } ins = $cursor replace_range(ins, ins, inserted) rangeset_add(rs, ins, ins + length(inserted)) set_cursor_pos(ins) } inserted = "" deleted = "" } pos = prev prev = prev - 1 } fileMerge_RS_makeModified() return window_name } # ============================================================================== # fileMerge_openOld(diffdata, info [, lang]): opens the first file of the diff # and colors the areas which were changed or deleted in the second. A # default language mode lang can be provided. # ============================================================================== define fileMerge_openOld { rsD = $fileMergeRSModNames[1] rsO = $fileMergeRSModNames[2] window_name = "" if ($n_args < 2 || $n_args > 3) return window_name diffdata = $1 info = $2 lang = "" if ($n_args == 3) lang = $3 pos = length(diffdata) if (substring(diffdata, pos - 1, pos) == "\n") pos-- # create a new window, make it active window = $file_path $file_name open(info[$fileMergeI_file1]) focus_window(info[$fileMergeI_file1]) raise_window() # set the language mode (if there is one to set) if (lang != "" && ($language_mode == "" || $language_mode == "Plain")) set_language_mode(lang) window_name = $file_path $file_name if (window == window_name) { dialog("Window: "window"\nCould not open "info[$fileMergeI_file1]) return "" } if ($modified) { dialog("Window: "window_name"\nOngoing modifications: no coloring") return window_name } set_cursor_pos($text_length) prev = pos - 1 inserted = "" deleted = "" # create the differently colored rangesets for the changed lines cols = $fileMergeRSColors rD = fileMerge_RS_color(rsD, cols[rsD]) # delete rO = fileMerge_RS_color(rsO, cols[rsO]) # change: old stuff # read the difference data from the end, backwards while (prev >= 0) { prev = search_string(diffdata, "\n", pos - 1, "backward") line = substring(diffdata, prev + 1, pos + 1) # include ending "\n" if (substring(line, 0, 2) == "> ") inserted = substring(line, 2) inserted else if (substring(line, 0, 2) == "< ") deleted = substring(line, 2) deleted else if (fileMerge_is_lines_info(line)) { lines = fileMerge_get_lines_info(line) operation = lines[0] # a:add, d:delete, c: change file1_from = lines[1] # from line for first file file1_to = lines[2] # to line for first file file2_from = lines[3] # from line for second file file2_to = lines[4] # to line for second file rs = "" # select line, put cursor at front goto_line_number(file1_from, 0) # deal with removed data opC = (operation == "c") if (operation == "d" || opC) { rs = (opC * rO) + (!opC * rD) ins = $cursor goto_line_number(file1_to + 1, 0) rangeset_add(rs, ins, $cursor) } # don't deal with added data inserted = "" deleted = "" } pos = prev prev = prev - 1 } fileMerge_RS_makeModified() return window_name } # ============================================================================== # fileMerge_display_file_mergeREV(diffdata, info [, lang]): opens the second # file of the diff and integrates info about the first file, using the # difference data in diffdata. This is done backwards. # It works "backward" with respect to fileMerge_display_file_mergeFWD(), # hence the name. # A default language mode lang can be provided. # This function is intended for use with CVS where you want to compare # available current images against CVS' last checkin (via 'cvs diff') # ============================================================================== define fileMerge_display_file_mergeREV { rsA = $fileMergeRSModNames[0] rsD = $fileMergeRSModNames[1] rsO = $fileMergeRSModNames[2] rsN = $fileMergeRSModNames[3] if ($n_args < 2 || $n_args > 3) return "" diffdata = $1 info = $2 lang = "" if ($n_args == 3) lang = $3 pos = length(diffdata) if (substring(diffdata, pos - 1, pos) == "\n") pos-- array = fileMerge_read_file(info[$fileMergeI_file2]) if (!array[]) return "" file2data = array[0] # create a new window, make it active new("tab") # can't give it a title ("Merged from: " info[$fileMergeI_diffline]) focus_window("last") raise_window() # set the language mode (if there is one to set) if (lang != "" && ($language_mode == "" || $language_mode == "Plain")) set_language_mode(lang) window_name = $file_path $file_name replace_range($cursor, $cursor, file2data) set_cursor_pos($cursor + length(file2data)) prev = pos - 1 inserted = "" deleted = "" # create the differently colored rangesets for the changed lines rA = fileMerge_RS_color(rsA, "green") # add rD = fileMerge_RS_color(rsD, "red") # delete rO = fileMerge_RS_color(rsO, "orange") # change: old stuff rN = fileMerge_RS_color(rsN, "cyan") # change: new stuff # read the difference data from the end, backwards while (prev >= 0) { prev = search_string(diffdata, "\n", pos - 1, "backward") line = substring(diffdata, prev + 1, pos + 1) # include ending "\n" if (substring(line, 0, 2) == "> ") inserted = substring(line, 2) inserted else if (substring(line, 0, 2) == "< ") deleted = substring(line, 2) deleted else if (fileMerge_is_lines_info(line)) { lines = fileMerge_get_lines_info(line) operation = lines[0] # a:add, d:delete, c: change file1_from = lines[1] # from line for first file file1_to = lines[2] # to line for first file file2_from = lines[3] # from line for second file file2_to = lines[4] # to line for second file rs = "" # select line, put cursor at front goto_line_number(file2_to + 1, 0) # deal with added data - present in data read from file2 opC = (operation == "c") if (operation == "a" || opC) { rs = (opC * rN) + (!opC * rA) ins = $cursor goto_line_number(file2_from, 0) rangeset_add(rs, $cursor, ins) } # deal with removed data if (operation == "d" || opC) { rs = (opC * rO) + (!opC * rD) ins = $cursor replace_range(ins, ins, deleted) rangeset_add(rs, ins, ins + length(deleted)) rangeset_add(rs, ins, $cursor) } inserted = "" deleted = "" } pos = prev prev = prev - 1 } fileMerge_RS_makeModified() return window_name } # ============================================================================== # fileMerge_openNew(diffdata, info [, lang]): opens the second file of the diff # and colors the areas which were changed or added to the first. A default # language mode lang can be provided. # ============================================================================== define fileMerge_openNew { rsA = $fileMergeRSModNames[0] rsN = $fileMergeRSModNames[3] window_name = "" if ($n_args < 2 || $n_args > 3) return window_name diffdata = $1 info = $2 lang = "" if ($n_args == 3) lang = $3 pos = length(diffdata) if (substring(diffdata, pos - 1, pos) == "\n") pos-- # create a new window, make it active window = $file_path $file_name open(info[$fileMergeI_file2]) focus_window(info[$fileMergeI_file2]) raise_window() # set the language mode (if there is one to set) if (lang != "" && ($language_mode == "" || $language_mode == "Plain")) set_language_mode(lang) window_name = $file_path $file_name if (window == window_name) { dialog("Window: "window"\nCould not open "info[$fileMergeI_file2]) return "" } if ($modified) { dialog("Window: "window_name"\nOngoing modifications: no coloring") return window_name } set_cursor_pos($text_length) prev = pos - 1 inserted = "" deleted = "" # create the differently colored rangesets for the changed lines cols = $fileMergeRSColors rA = fileMerge_RS_color(rsA, cols[rsA]) # add rN = fileMerge_RS_color(rsN, cols[rsN]) # change: new stuff # read the difference data from the end, backwards while (prev >= 0) { prev = search_string(diffdata, "\n", pos - 1, "backward") line = substring(diffdata, prev + 1, pos + 1) # include ending "\n" if (substring(line, 0, 2) == "> ") inserted = substring(line, 2) inserted else if (substring(line, 0, 2) == "< ") deleted = substring(line, 2) deleted else if (fileMerge_is_lines_info(line)) { lines = fileMerge_get_lines_info(line) operation = lines[0] # a:add, d:delete, c: change file1_from = lines[1] # from line for first file file1_to = lines[2] # to line for first file file2_from = lines[3] # from line for second file file2_to = lines[4] # to line for second file rs = "" # select line, put cursor at front goto_line_number(file2_to + 1, 0) # deal with added data - present in data read from file2 opC = (operation == "c") if (operation == "a" || opC) { rs = (opC * rN) + (!opC * rA) ins = $cursor goto_line_number(file2_from, 0) rangeset_add(rs, $cursor, ins) } # don't deal with removed data inserted = "" deleted = "" } pos = prev prev = prev - 1 } fileMerge_RS_makeModified() return window_name } # ============================================================================== # fileMerge_diff_details(title): presents information about the differences # found between files for the merged result (assumed to be in the current # window) in a string. The parameter title is prefixed on the front. # ============================================================================== define fileMerge_diff_details { title = $1 dlgtext = title for (i in $fileMergeRSModNames) { L = $fileMergeRSModNames[i] info = rangeset_info(fileMerge_RS_recolor(L)) if (info["defined"]) { if (info["count"] > 0) dlgtext = dlgtext L ": " fileMerge_nameRSbg(info["color"]) \ " (" info["count"] " ranges)\n" } } return dlgtext } # ============================================================================== # fileMerge_rework_display(ins_pos, diffdata, filedata, rA, rD, rO, rN): takes # a broken-up set of changes (created by diff listed in diffdata) and # merges them into the current text at ins_pos. filedata contains the # text of the old (left hand) file of broken-up change text. rA, rD, rO, # rN are the various rangesets. # ============================================================================== define fileMerge_rework_display { ins_pos = $1 diffdata = split(substring($2, 0, -1), "\n") filedata = split(substring($3, 0, -1), "\n") rA = $4 rD = $5 rO = $6 rN = $7 d_index = diffdata[] f_index = filedata[] - 1 diff_new = "" diff_old = "" while (d_index >= 1) { d_index-- s = diffdata[d_index] len = length(s) if (substring(s, 0, 2) == "> ") { if (len == 2) diff_new = "\n" diff_new else diff_new = substring(s, 2, len) diff_new } else if (substring(s, 0, 2) == "< ") { if (len == 2) diff_old = "\n" diff_old else diff_old = substring(s, 2, len) diff_old } else if (fileMerge_is_lines_info(s"\n")) { lines = fileMerge_get_lines_info(s"\n") operation = lines[0] # a:add, d:delete, c: change file1_from = lines[1] # from line for first file file1_to = lines[2] # to line for first file file2_from = lines[3] # from line for second file file2_to = lines[4] # to line for second file # right: first assemble a string of data which follows the diff s = "" while (f_index >= file1_to) { t = filedata[f_index] f_index-- if (t == "") t = "\n" s = t s } replace_range(ins_pos, ins_pos, s) # now for the new stuff if (diff_old == "") r = rA else r = rN replace_range(ins_pos, ins_pos, diff_new) rangeset_add(r, ins_pos, ins_pos + length(diff_new)) # now for the old stuff if (diff_new == "") r = rD else r = rO replace_range(ins_pos, ins_pos, diff_old) rangeset_add(r, ins_pos, ins_pos + length(diff_old)) # now adjust f_index if (operation == "a") f_index = file1_from - 1 else f_index = file1_from - 2 diff_new = "" diff_old = "" } } s = "" while (f_index >= 0) { t = filedata[f_index] f_index-- if (t == "") t = "\n" s = t s } replace_range(ins_pos, ins_pos, s) } # ============================================================================== # fileMerge_rework_changes([index]): takes each pair of old/new change rangeset # ranges and analyses them on a word by word (rather than line by line) # basis. If index is provided and is in range, only the individual change # identified by the ranges it addresses in the old/new change rangesets # will be handled. # ============================================================================== define fileMerge_rework_changes { index = -1 if ($n_args > 0) index = 0 + $1 rsA = $fileMergeRSModNames[0] rsD = $fileMergeRSModNames[1] rsO = $fileMergeRSModNames[2] rsN = $fileMergeRSModNames[3] rA = fileMerge_RS_recolor(rsA, "green", 0) # add rD = fileMerge_RS_recolor(rsD, "red", 0) # delete rO = fileMerge_RS_recolor(rsO, "orange", 0) # change: old stuff rN = fileMerge_RS_recolor(rsN, "cyan", 0) # change: new stuff # find_str = "(.)" # compare char-by-char? # find_str = "([_a-zA-Z0-9]+|[^_a-zA-Z0-9 \t]+|[ \t]+)" # compare word-by-word # find_str = "([_a-zA-Z0-9]+|[^_a-zA-Z0-9 \t]+|[ \t])" # compare word-by-word find_str = "(\\w+|\\W)" # compare word-by-word, rest compared char-by-char repl_str = "\\1\n" f_old = $fileMerge_rework_change_file_root "old" f_new = $fileMerge_rework_change_file_root "new" first_index = 1 last_index = rangeset_info(rO)["count"] if (index > 0) { first_index = index last_index = index } for (i = last_index; i >= first_index; i--) { o = rangeset_range(rO, i) o_beg = o["start"] o_end = o["end"] old_txt = get_range(o_beg, o_end) n = rangeset_range(rN, i) n_beg = n["start"] n_end = n["end"] new_txt = get_range(n_beg, n_end) # only carry on if we have complete lines, with new immediately after old if (!((o_beg == 0 || get_range(o_beg - 1, o_beg) == "\n") && \ substring(old_txt, -1) == "\n" && \ n_beg == o_end && \ substring(new_txt, -1) == "\n")) continue # separate words/punctuation sequences onto separate lines # (end of line becomes an empty line) if (search_string(old_txt, find_str, 0, "regex") != -1) old_txt = replace_in_string(old_txt, find_str, repl_str, "regex") if (search_string(new_txt, find_str, 0, "regex") != -1) new_txt = replace_in_string(new_txt, find_str, repl_str, "regex") # write out the two selections to separate temporary files if (!write_file(old_txt, f_old) || !write_file(new_txt, f_new)) { dialog("Failed to write to temporary files for reworked diff\n" \ " old text file: " f_old \ " new text file: " f_new, "Cancel") break } # position it in window set_cursor_pos(n_end) set_cursor_pos(o_beg) # diff the result diff_txt = shell_command("diff "f_old" "f_new, "") fileMerge_rework_display(n_end, diff_txt, old_txt, rA, rD, rO, rN) replace_range(o_beg, n_end, "") } shell_command("rm "f_old" "f_new, "") fileMerge_RS_makeModified() } # ============================================================================== # fileMerge_CopyLeftRight(fLeft, fRight): copies file1 to file2 or vice-versa # according to choices picked from dialogs. Returns an empty array on # failure; a two-entry array [0] = fromFile / [1] = toFile if successful. # ============================================================================== define fileMerge_CopyLeftRight { fLeft = $1 fRight = $2 # perform a copy operation cpwhat = dialog("Copy:\n\n Left: " fLeft "\n\n Right: " fRight "\n\n", \ "Left->Right", "Right->Left", "Cancel") if (cpwhat < 1 || cpwhat > 2) return $empty_array copiedOK = 0 # which direction? if (cpwhat == 1) { fromFile = fLeft toFile = fRight } else { fromFile = fRight toFile = fLeft } # pick up source data array = fileMerge_read_file(fromFile) if (!array[]) { dialog("FAILURE to copy from\n "fromFile"\nto\n "toFile"\n\n"\ "Could not read "fromFile) return $empty_array } filebuffer = array[0] if (dialog("Copying from\n "fromFile"\nto\n "toFile"\n(size " \ length(filebuffer) ")\n\nAre you sure?", \ "OK", "Cancel") == 1) { if (!fileMerge_write_file(filebuffer, toFile)) { dialog("FAILURE to copy from\n "fromFile"\nto\n "toFile"\n\n" \ "Could not write "toFile) return $empty_array } else copiedOK = 1 } return make_arraylist(fromFile, toFile) } # ============================================================================== # fileMerge_from_diff([rework [, rev]]): interprets the current window as the # output of a plain diff command, such as "diff file-old file-new". It # expects the diff command to be present. (Fine for executing diff with # the keypad enter key or Shell->Execute Command Line option.) # # If the rework parameter is present and "true", change reworking will # be applied. # # If the rev parameter is present and "true", the base file used for # merging will be the right hand file, not the left. # # If you want multiple differences to be calculated, you should issue # commands like (in sh): # for f in *.c; do [ -f $f.old ] && (echo diff $f.old $f; diff $f.old $f); done # For directory compares, diff puts in the "echo" part in itself. # ============================================================================== define fileMerge_from_diff { rsS = $fileMergeRSDiffParts[0] # diff spec rsI = $fileMergeRSDiffParts[1] # ignored rsH = $fileMergeRSDiffParts[2] # current diff rsF = $fileMergeRSDiffParts[3] # diff output rework = 0 if ($n_args > 0) rework = ($1 != "" && $1 != "0") rev = 0 if ($n_args > 1) rev = ($2 != "" && $2 != "0") this_window = $file_path $file_name difftext = get_range(0, $text_length) diffinfo = fileMerge_extract_diffinfo(difftext) i = 0 diffpos = 0 startpos = 0 endpos = 0 # color the different sections of the diff output using diffinfo cols = $fileMergeRSColors rS = fileMerge_RS_color(rsS, cols[rsS]) # diff specification headings rI = fileMerge_RS_color(rsI, cols[rsI]) # invalid/ignored lines for (diff_num = 0; diff_num in diffinfo; diff_num++) { info = diffinfo[diff_num] diffpos = info[$fileMergeI_diffpos] rangeset_add(rI, endpos, diffpos) startpos = info[$fileMergeI_startpos] endpos = info[$fileMergeI_endpos] rangeset_add(rS, diffpos, startpos) } rangeset_add(rI, endpos, $text_length) # loop over diff do_it_again = 1 infos = $empty_array nInfos = 0 while (do_it_again) { # ask the user which diff he wants to analyse if (nInfos == 0) { infos = fileMerge_ask_for_mdiff_line(diffinfo) nInfos = infos[] if (nInfos == 0) break } # read off each info array from the back --nInfos info = infos[nInfos] delete infos[nInfos] # make sure the window containing diff information is current focus_window(this_window) raise_window() lang = $language_mode diffpos = info[$fileMergeI_diffpos] startpos = info[$fileMergeI_startpos] endpos = info[$fileMergeI_endpos] operation = info[$fileMergeI_operation] # forget and re-establish range sets F and H for diff output highlights cols = $fileMergeRSColors rH = fileMerge_RS_color(rsH, cols[rsH]) # current diff "header" line rF = fileMerge_RS_color(rsF, cols[rsF]) # diff output to handle rangeset_add(rH, diffpos, startpos) rangeset_add(rF, startpos, endpos) set_cursor_pos(endpos) # reposition screen set_cursor_pos(diffpos) file1 = info[$fileMergeI_file1] file2 = info[$fileMergeI_file2] # button numbers d_dismiss = 1 d_another = 2 d_forget = 3 d_copy_files = 4 d_open_files = 5 # must be last what = d_dismiss if (operation == "Analyse" || operation == "Locate") { # extract diff data to analyse diffdata = fileMerge_get_diff_data(difftext, info) if (operation != "Locate") { # create the result of merging differences into a new window if (rev) { # try reverse merge first (loading the right/second/new file) m_win = fileMerge_display_file_mergeREV(diffdata, info, lang) if (m_win == "") m_win = fileMerge_display_file_mergeFWD(diffdata, info, lang) } else { # try forward merge first (loading the left/first/old file) m_win = fileMerge_display_file_mergeFWD(diffdata, info, lang) if (m_win == "") m_win = fileMerge_display_file_mergeREV(diffdata, info, lang) } merge_window = m_win if (merge_window == "") { dialog("From directory "$file_path"\n\n" \ "Could not find either of the files:\n\n" \ " " file1 "\n" \ " " file2 "\n") continue } # do we rework? (the focus is now on merge_window) if (rework) fileMerge_rework_changes() } # tell the user what has been found dlgtext = info[$fileMergeI_diffline] \ "\n\n Left: " file1 "\n\n Right: " file2 "\n\n" dlgtext = fileMerge_diff_details(dlgtext) # # the following loop provides only one opportunity to open left/right # done_left = 0 # done_right = 0 # # while (1) # { # # allow opening of left and right files # if (!done_left && !done_right) # what = dialog(dlgtext, "Cancel", "Another", "Forget diff", "Copy", \ # "Open left", "Open right") # else if (!done_left && done_right) # what = dialog(dlgtext, "Cancel", "Another", "Forget diff", "Copy", \ # "Open left") # else if (done_left && !done_right) # { # what = dialog(dlgtext, "Cancel", "Another", "Forget diff", "Copy", \ # "Open right") # if (what == d_open_files + 0) # what = d_open_files + 1 # } # else # if (done_left && done_right) # what = dialog(dlgtext, "Cancel", "Another", "Forget diff") # # # perform opening of left and right files, then loop # if (what == d_open_files + 0) # { # # open(file1) # fileMerge_openOld(diffdata, info) # done_left = 1 # } # else if (what == d_open_files + 1) # { # # open(file2) # fileMerge_openNew(diffdata, info) # done_right = 1 # } # else if (what == d_forget) # { # operation = "Forget" # break # } # else if (what == d_copy_files) # { # operation = "Copy" # break # } # else # { # operation = "" # break # } # } if (nInfos > 0) what = d_another while (nInfos == 0) { # allow opening of left and right files what = dialog(dlgtext, "Cancel", "Another", "Forget diff", "Copy", \ "Open left", "Open right") if (what == d_open_files + 0) { # open(file1) fileMerge_openOld(diffdata, info, lang) } else if (what == d_open_files + 1) { # open(file2) fileMerge_openNew(diffdata, info, lang) } else if (what == d_forget) { operation = "Forget" break } else if (what == d_copy_files) { operation = "Copy" break } else { operation = "" break } } } if (operation == "Copy") { # perform a copy operation fromTo = fileMerge_CopyLeftRight(file1, file2) if (fromTo[]) { if (dialog("Copied "fromTo[0]" to "fromTo[1]"\n"\ "Delete difference information?", "Yes", "No") == 1) operation = "Forget" } what = d_another } if (operation == "Forget") { # remove the diff information for the current file pair # in rangesets H and F #yyy=$file_path $file_name #zzz=focus_window(this_window) #dialog("Was in "yyy"\nDifferences in "this_window"\nfocus_window: "zzz"\nCurr="$file_path $file_name) focus_window(this_window) raise_window() if (rangeset_info(rH)["defined"] && rangeset_info(rF)["defined"]) { if (rangeset_info(rH)["count"] == 1 && rangeset_info(rF)["count"] == 1) { H_begin = rangeset_range(rH, 1)["start"] F_end = rangeset_range(rF, 1)["end"] replace_range(H_begin, F_end, "") } } # re-evaluate diffinfo difftext = get_range(0, $text_length) diffinfo = fileMerge_extract_diffinfo(difftext) what = d_another } do_it_again = (what >= d_another) } }