diff --git a/README.md b/README.md index fa5aba3..1ffd176 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ Show differences between files in a two column view. changes -L LABELS, --label=LABELS override file labels with arbitrary tags. Use twice, - one for each file + one for each file. You may include the formatting + strings '{path}' and '{basename}' -N, --line-numbers generate output with line numbers. Not compatible with the 'exclude-lines' option. --no-bold use non-bold colors; recommended for solarized @@ -66,6 +67,7 @@ Show differences between files in a two column view. --show-all-spaces color all non-matching whitespace including that which is not needed for drawing the eye to changes. Slow, ugly, displays all changes + --show-no-spaces don't color whitespace-only changes --tabsize=TABSIZE tab stop spacing -t, --truncate truncate long lines instead of wrapping them -u, --patch generate patch. This is always true, and only exists @@ -75,14 +77,23 @@ Show differences between files in a two column view. with --whole-file -W, --whole-file show the whole file instead of just changed lines and context + -P, --permissions compare the file permissions as well as the content of + the file --strip-trailing-cr strip any trailing carriage return at the end of an input line --color-map=COLOR_MAP choose which colors are used for which items. Default is --color-map='add:green_bold,change:yellow_bold,desc - ription:blue,meta:magenta,separator:blue,subtract:red_ - bold'. You don't have to override all of them: - '--color-map=separator:white,description:cyan + ription:blue,line-numbers:white,meta:magenta,permissio + ns:yellow,separator:blue,subtract:red_bold'. You + don't have to override all of them: '--color- + map=separator:white,description:cyan + --is-git-diff Show the real file name when displaying git-diff + result + -x PATH, --exclude=PATH + exclude files that match PATH + -R RESTRICT, --restrict=RESTRICT + Only color text that matches this regex ``` ## Using with Git diff --git a/icdiff b/icdiff index b2a1306..bb43e73 100755 --- a/icdiff +++ b/icdiff @@ -103,6 +103,7 @@ class ConsoleDiff(object): highlight=False, truncate=False, strip_trailing_cr=False, + restrict=False, ): """ConsoleDiff instance initializer @@ -123,6 +124,7 @@ class ConsoleDiff(object): self.highlight = highlight self.strip_trailing_cr = strip_trailing_cr self.truncate = truncate + self.restrict = restrict if wrapcolumn is None: if not line_numbers: @@ -182,7 +184,57 @@ class ConsoleDiff(object): return sum(width(c) for c in s) - def _split_line(self, data_list, line_num, text): + def _mark_clipper(self, origin, marked): + xranges = [ + (i.start(), i.end()) for i in re.finditer(self.restrict, origin) + ] + result = "" + c0 = 0 + + def wipe_all(s): + return replace_all({"\0+": "", "\0-": "", "\0^": "", "\1": ""}, s) + + if not xranges: + return wipe_all(marked) + + # we can interpolate restricted and marked text from left to right. + # See https://docs.python.org/3/library/re.html#re.finditer + rex = re.escape("\0") + "[^" + re.escape("\0") + "]+" + re.escape("\1") + for i, m in enumerate(re.finditer(rex, marked)): + m0, m1, m2, m3 = m.start(), m.start() + 2, m.end() - 1, m.end() + offset = i * 3 + 2 + c1 = c0 + + for xstart, xend in xranges: + x1, x2 = xstart + offset, xend + offset + restricted = range(x1, x2 + 1) # include the last + + if m1 >= x1 and m2 <= x2: + result += marked[c0:m3] + elif m1 < x1 and m2 in restricted: + result += marked[c0:m0] + marked[m1:x1] + result += marked[m0:m1] + marked[x1:m3] + elif m1 in restricted and m2 > x2: + result += marked[c0:x2] + marked[m2:m3] + result += marked[x2:m2] + elif m1 < x1 and m2 > x2: + result += marked[c0:m0] + marked[m1:x1] + result += marked[m0:m1] + marked[x1:x2] + result += marked[m2:m3] + marked[x2:m2] + else: + continue + + c0 = m3 + break + + if c0 == c1: + result += wipe_all(marked[c0:m3]) + c0 = m3 + + result += marked[c0:] + return result + + def _split_line(self, orig_list, data_list, line_num, text): """Builds list of text lines by splitting text lines at wrap point This function will determine if the input text line needs to be @@ -192,6 +244,7 @@ class ConsoleDiff(object): the second part of the split line to further split it. """ + i = 0 while True: # if blank line or context separator, just add it to the output # list @@ -205,6 +258,11 @@ class ConsoleDiff(object): self._display_len(text) - (text.count("\0") * 3) <= self._wrapcolumn ): + if self.restrict: + orig_text = orig_list[line_num - 1] + text = self._mark_clipper(orig_text[i:], text) + + line_num = line_num if not i else ">" data_list.append((line_num, text)) return @@ -236,6 +294,10 @@ class ConsoleDiff(object): line1 = line1 + "\1" line2 = "\0" + mark + line2 + if self.restrict: + orig_text = orig_list[line_num - 1] + line1 = self._mark_clipper(orig_text[:i], line1) + # tack on first line onto the output list data_list.append((line_num, line1)) @@ -243,10 +305,9 @@ class ConsoleDiff(object): # unless truncate is set if self.truncate: return - line_num = ">" text = line2 - def _line_wrapper(self, diffs): + def _line_wrapper(self, fromlines, tolines, diffs): """Returns iterator that splits (wraps) mdiff text lines""" # pull from/to data and flags from mdiff iterator @@ -259,8 +320,8 @@ class ConsoleDiff(object): # for each from/to line split it at the wrap column to form # list of text lines. fromlist, tolist = [], [] - self._split_line(fromlist, fromline, fromtext) - self._split_line(tolist, toline, totext) + self._split_line(fromlines, fromlist, fromline, fromtext) + self._split_line(tolines, tolist, toline, totext) # yield from/to line in pairs inserting blank lines as # necessary when one side has more wrapped lines while fromlist or tolist: @@ -392,7 +453,7 @@ class ConsoleDiff(object): # set up iterator to wrap lines that exceed desired width if self._wrapcolumn: - diffs = self._line_wrapper(diffs) + diffs = self._line_wrapper(fromlines, tolines, diffs) diffs = self._collect_lines(diffs) for left, right in self._generate_table( @@ -726,10 +787,18 @@ def create_option_parser(): parser.add_option( "-x", "--exclude", - metavar="PAT", + metavar="PATH", action="append", default=[], - help="exclude files that match PAT", + help="exclude files that match PATH", + ) + parser.add_option( + "-R", + "--restrict", + action="store", + type="string", + dest="restrict", + help="Only color text that matches this regex", ) return parser @@ -1012,6 +1081,7 @@ def diff_files(options, a, b): tabsize=int(options.tabsize), truncate=options.truncate, strip_trailing_cr=options.strip_trailing_cr, + restrict=options.restrict, ) for line in cd.make_table( lines_a, diff --git a/test.sh b/test.sh index 1dd6c4b..ba0462f 100755 --- a/test.sh +++ b/test.sh @@ -102,11 +102,10 @@ function check_git_diff() { FIRST_TIME_CHECK_GIT_DIFF=false # Set default args when first time check git diff yes | git difftool --extcmd icdiff > /dev/null - git config --global icdiff.options '--cols=80' export PATH="$(pwd)":$PATH fi local tmp=/tmp/git-icdiff.output - git icdiff $1 $2 &> $tmp + git -c icdiff.options='--cols=80' icdiff $1 $2 &> $tmp if ! diff $tmp $gitdiff; then echo "Got: ($tmp)" cat $tmp @@ -162,6 +161,10 @@ check_gold 2 gold-bad-encoding.txt tests/input-{1,2}.txt --encoding=nonexistend_ check_gold 0 gold-recursive-with-exclude.txt --recursive -x c tests/{a,b} --cols=80 check_gold 1 gold-recursive-with-exclude2.txt --recursive -x 'excl*' tests/test-with-exclude/{a,b} --cols=80 check_gold 0 gold-exit-process-sub tests/input-1.txt <(cat tests/input-1.txt) --no-headers --cols=80 +check_gold 1 gold-restrict-clip-none.txt tests/input-restrict-0{1,2}.txt --restrict="(%|A).*(%|A)?" --cols=100 +check_gold 1 gold-restrict-clip-lhs.txt tests/input-restrict-0{1,2}.txt --restrict="(;|O).+;" --cols=100 +check_gold 1 gold-restrict-clip-rhs.txt tests/input-restrict-0{1,2}.txt --restrict=":.+(:|O)" --cols=100 +check_gold 1 gold-restrict-clip-both.txt tests/input-restrict-0{1,2}.txt --restrict="Q.+Q" --cols=100 rm -f tests/permissions-{a,b} touch tests/permissions-{a,b} diff --git a/tests/gold-restrict-clip-both.txt b/tests/gold-restrict-clip-both.txt new file mode 100644 index 0000000..3757912 --- /dev/null +++ b/tests/gold-restrict-clip-both.txt @@ -0,0 +1,7 @@ +tests/input-restrict-01.txt tests/input-restrict-02.txt +__________%______________________%___________ __________%________AXXXXXA_______%___________ +___________________%_____%___________________ ___________________AAAAAAA___________________ +______________________%______________________ ______________________A______________________ +______________________;__________;___________ ___________________XXXOXXX_______;___________ +__________:___________:______________________ __________:________XXXOXXX___________________ +_____________________________________________ __________ QQQ ___________ diff --git a/tests/gold-restrict-clip-lhs.txt b/tests/gold-restrict-clip-lhs.txt new file mode 100644 index 0000000..650f67a --- /dev/null +++ b/tests/gold-restrict-clip-lhs.txt @@ -0,0 +1,7 @@ +tests/input-restrict-01.txt tests/input-restrict-02.txt +__________%______________________%___________ __________%________AXXXXXA_______%___________ +___________________%_____%___________________ ___________________AAAAAAA___________________ +______________________%______________________ ______________________A______________________ +______________________;__________;___________ ___________________XXXOXXX_______;___________ +__________:___________:______________________ __________:________XXXOXXX___________________ +_____________________________________________ __________ QQQ ___________ diff --git a/tests/gold-restrict-clip-none.txt b/tests/gold-restrict-clip-none.txt new file mode 100644 index 0000000..50fb93c --- /dev/null +++ b/tests/gold-restrict-clip-none.txt @@ -0,0 +1,7 @@ +tests/input-restrict-01.txt tests/input-restrict-02.txt +__________%______________________%___________ __________%________AXXXXXA_______%___________ +___________________%_____%___________________ ___________________AAAAAAA___________________ +______________________%______________________ ______________________A______________________ +______________________;__________;___________ ___________________XXXOXXX_______;___________ +__________:___________:______________________ __________:________XXXOXXX___________________ +_____________________________________________ __________ QQQ ___________ diff --git a/tests/gold-restrict-clip-rhs.txt b/tests/gold-restrict-clip-rhs.txt new file mode 100644 index 0000000..da7a787 --- /dev/null +++ b/tests/gold-restrict-clip-rhs.txt @@ -0,0 +1,7 @@ +tests/input-restrict-01.txt tests/input-restrict-02.txt +__________%______________________%___________ __________%________AXXXXXA_______%___________ +___________________%_____%___________________ ___________________AAAAAAA___________________ +______________________%______________________ ______________________A______________________ +______________________;__________;___________ ___________________XXXOXXX_______;___________ +__________:___________:______________________ __________:________XXXOXXX___________________ +_____________________________________________ __________ QQQ ___________ diff --git a/tests/input-restrict-01.txt b/tests/input-restrict-01.txt new file mode 100644 index 0000000..932c0cf --- /dev/null +++ b/tests/input-restrict-01.txt @@ -0,0 +1,6 @@ +__________%______________________%___________ +___________________%_____%___________________ +______________________%______________________ +______________________;__________;___________ +__________:___________:______________________ +_____________________________________________ diff --git a/tests/input-restrict-02.txt b/tests/input-restrict-02.txt new file mode 100644 index 0000000..5474965 --- /dev/null +++ b/tests/input-restrict-02.txt @@ -0,0 +1,6 @@ +__________%________AXXXXXA_______%___________ +___________________AAAAAAA___________________ +______________________A______________________ +___________________XXXOXXX_______;___________ +__________:________XXXOXXX___________________ +__________ QQQ ___________