feat: Add --diff-additions and --diff-deletions options with line highlighting
This commit is contained in:
parent
77ecf27131
commit
b5c852e544
145
gtm
145
gtm
|
|
@ -4,6 +4,7 @@ import curses
|
|||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
VERSION = "2025-06-07"
|
||||
|
||||
|
|
@ -17,7 +18,55 @@ def get_file_at_commit(commit_hash, filename):
|
|||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.stdout.splitlines()
|
||||
|
||||
def main(stdscr, filename):
|
||||
def get_diff_info(current_commit, prev_commit, filename):
|
||||
"""Get diff information between two commits for a file"""
|
||||
if not prev_commit:
|
||||
# For the first commit, we can't compare with a previous one
|
||||
return [], []
|
||||
|
||||
# Get additions
|
||||
cmd_additions = ['git', 'diff', '--unified=0', prev_commit, current_commit, '--', filename]
|
||||
result_additions = subprocess.run(cmd_additions, capture_output=True, text=True)
|
||||
|
||||
added_lines = []
|
||||
deleted_lines = []
|
||||
|
||||
for line in result_additions.stdout.splitlines():
|
||||
# Parse the diff output to find line numbers of additions and deletions
|
||||
if line.startswith('@@'):
|
||||
# Parse the hunk header to get line numbers
|
||||
parts = line.split()
|
||||
if len(parts) >= 3:
|
||||
# Format is like @@ -a,b +c,d @@
|
||||
# We're interested in the +c,d part for additions
|
||||
add_part = parts[2][1:] # Remove the + sign
|
||||
if ',' in add_part:
|
||||
start_line, count = map(int, add_part.split(','))
|
||||
else:
|
||||
start_line, count = int(add_part), 1
|
||||
|
||||
# For deletions, we look at the -a,b part
|
||||
del_part = parts[1][1:] # Remove the - sign
|
||||
if ',' in del_part:
|
||||
del_start, del_count = map(int, del_part.split(','))
|
||||
else:
|
||||
del_start, del_count = int(del_part), 1
|
||||
elif line.startswith('+') and not line.startswith('+++'):
|
||||
# This is an added line
|
||||
added_lines.append((start_line, line[1:]))
|
||||
start_line += 1
|
||||
elif line.startswith('-') and not line.startswith('---'):
|
||||
# This is a deleted line
|
||||
deleted_lines.append((del_start, line[1:]))
|
||||
del_start += 1
|
||||
elif not line.startswith('+') and not line.startswith('-') and not line.startswith('@@'):
|
||||
# Context line, increment both counters
|
||||
start_line += 1
|
||||
del_start += 1
|
||||
|
||||
return added_lines, deleted_lines
|
||||
|
||||
def main(stdscr, filename, show_additions=False, show_deletions=False):
|
||||
curses.curs_set(0)
|
||||
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
||||
curses.mouseinterval(0)
|
||||
|
|
@ -28,6 +77,8 @@ def main(stdscr, filename):
|
|||
curses.use_default_colors() # Use terminal's default colors
|
||||
curses.init_pair(1, 5, 7) # Focused status bar
|
||||
curses.init_pair(2, curses.COLOR_WHITE, 8) # Unfocused status bar
|
||||
curses.init_pair(3, curses.COLOR_GREEN, -1) # Added lines
|
||||
curses.init_pair(4, curses.COLOR_RED, -1) # Deleted lines
|
||||
|
||||
# Initialize key variable
|
||||
key = 0
|
||||
|
|
@ -45,6 +96,17 @@ def main(stdscr, filename):
|
|||
commit_hash = commits[selected_commit].split()[0]
|
||||
file_lines = get_file_at_commit(commit_hash, filename)
|
||||
|
||||
# Get previous commit hash for diff
|
||||
prev_commit_hash = None
|
||||
if selected_commit < len(commits) - 1:
|
||||
prev_commit_hash = commits[selected_commit + 1].split()[0]
|
||||
|
||||
# Get diff information if needed
|
||||
added_lines = []
|
||||
deleted_lines = []
|
||||
if show_additions or show_deletions:
|
||||
added_lines, deleted_lines = get_diff_info(commit_hash, prev_commit_hash, filename)
|
||||
|
||||
# Enable nodelay for smoother scrolling
|
||||
stdscr.nodelay(True)
|
||||
|
||||
|
|
@ -57,6 +119,14 @@ def main(stdscr, filename):
|
|||
commit_hash = commits[selected_commit].split()[0]
|
||||
file_lines = get_file_at_commit(commit_hash, filename)
|
||||
|
||||
# Update diff information when commit changes
|
||||
prev_commit_hash = None
|
||||
if selected_commit < len(commits) - 1:
|
||||
prev_commit_hash = commits[selected_commit + 1].split()[0]
|
||||
|
||||
if show_additions or show_deletions:
|
||||
added_lines, deleted_lines = get_diff_info(commit_hash, prev_commit_hash, filename)
|
||||
|
||||
max_scroll = max(0, len(file_lines) - (height - 1))
|
||||
scroll_offset = min(scroll_offset, max_scroll)
|
||||
visible_lines = file_lines[scroll_offset:scroll_offset + height - 1]
|
||||
|
|
@ -95,7 +165,33 @@ def main(stdscr, filename):
|
|||
for i, line in enumerate(visible_lines):
|
||||
# Only draw what fits in the window
|
||||
if i < height - 1:
|
||||
stdscr.addnstr(i, divider_col + 2, line, right_width)
|
||||
line_num = i + scroll_offset + 1 # 1-based line number
|
||||
|
||||
# Check if this is an added line
|
||||
is_added = False
|
||||
if show_additions:
|
||||
for added_line_num, _ in added_lines:
|
||||
if added_line_num == line_num:
|
||||
is_added = True
|
||||
break
|
||||
|
||||
# Display the line with appropriate formatting
|
||||
if is_added:
|
||||
stdscr.addstr(i, divider_col + 2, "+ ", curses.color_pair(3))
|
||||
stdscr.addnstr(i, divider_col + 4, line, right_width - 2, curses.color_pair(3))
|
||||
else:
|
||||
stdscr.addnstr(i, divider_col + 2, line, right_width)
|
||||
|
||||
# Display deleted lines if enabled
|
||||
if show_deletions:
|
||||
# Check if there are deleted lines after the current line
|
||||
for del_line_num, del_line_content in deleted_lines:
|
||||
if del_line_num == line_num:
|
||||
# Only show if we have space
|
||||
if i + 1 < height - 1:
|
||||
i += 1 # Move to next line for displaying the deleted content
|
||||
stdscr.addstr(i, divider_col + 2, "- ", curses.color_pair(4))
|
||||
stdscr.addnstr(i, divider_col + 4, del_line_content, right_width - 2, curses.color_pair(4))
|
||||
|
||||
# Status bars for both panes
|
||||
visible_height = height - 1 # Reserve 1 line for the status bar
|
||||
|
|
@ -226,30 +322,37 @@ def show_help():
|
|||
print("A \"Git Time Machine\" for viewing file history.")
|
||||
print()
|
||||
print("Usage:")
|
||||
print("\tgtm FILENAME")
|
||||
print("\tgtm [OPTIONS] FILENAME")
|
||||
print()
|
||||
print("Flags:")
|
||||
print("Options:")
|
||||
print("\t--diff-additions\thighlight newly added lines in green")
|
||||
print("\t--diff-deletions\tshow deleted lines in red")
|
||||
print("\t-v, --version\tshow version number")
|
||||
print("\t-h, --help\tshow this help")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] == "-v" or sys.argv[1] == "--version":
|
||||
print(f"gtm version {VERSION}")
|
||||
sys.exit(0)
|
||||
elif sys.argv[1] == "-h" or sys.argv[1] == "--help":
|
||||
show_help()
|
||||
sys.exit(0)
|
||||
|
||||
filename = sys.argv[1]
|
||||
|
||||
# Check if the file exists
|
||||
if not os.path.isfile(filename):
|
||||
print(f"Error: File '{filename}' does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
curses.wrapper(main, filename)
|
||||
else:
|
||||
parser = argparse.ArgumentParser(add_help=False, description="A Git Time Machine for viewing file history.")
|
||||
parser.add_argument("--diff-additions", action="store_true", help="Highlight newly added lines in green")
|
||||
parser.add_argument("--diff-deletions", action="store_true", help="Show deleted lines in red")
|
||||
parser.add_argument("-v", "--version", action="store_true", help="Show version number")
|
||||
parser.add_argument("-h", "--help", action="store_true", help="Show help information")
|
||||
parser.add_argument("filename", nargs="?", help="File to view history for")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print(f"gtm version {VERSION}")
|
||||
sys.exit(0)
|
||||
elif args.help or not args.filename:
|
||||
show_help()
|
||||
sys.exit(0 if args.help else 1)
|
||||
|
||||
filename = args.filename
|
||||
|
||||
# Check if the file exists
|
||||
if not os.path.isfile(filename):
|
||||
print(f"Error: File '{filename}' does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
curses.wrapper(main, filename, args.diff_additions, args.diff_deletions)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue