From e4b65b98d1d8b5eafc1f930c26803d9140d90e78 Mon Sep 17 00:00:00 2001 From: "n loewen (aider)" Date: Sun, 8 Jun 2025 02:20:16 +0100 Subject: [PATCH] feat: Add line numbers with color-coded display and toggle option --- gtm | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/gtm b/gtm index 8b11863..2f240f6 100755 --- a/gtm +++ b/gtm @@ -133,6 +133,9 @@ class AppState: # Line wrapping settings wrap_lines: bool = True + # Line numbers settings + show_line_numbers: bool = False + # Change navigation change_blocks: List[Tuple[int, int]] = field(default_factory=list) # (start_line, end_line) of each change block current_change_idx: int = -1 # -1 means no change is selected @@ -356,6 +359,18 @@ def jump_to_prev_change(state: AppState) -> AppState: def draw_right_pane(stdscr, state): # If sidebar is hidden, right pane starts at column 0 right_start = 0 if not state.show_sidebar else state.divider_col + 2 + + # Calculate line number width if enabled + line_num_width = 0 + if state.show_line_numbers: + # Width based on the number of digits in the largest line number + max_line_num = len(state.file_lines) + line_num_width = len(str(max_line_num)) + 1 # +1 for spacing + + # Adjust right pane start position and width for line numbers + if state.show_line_numbers: + right_start += line_num_width + right_width = state.width - right_start - 1 max_scroll = max(0, len(state.file_lines) - (state.height - 1)) @@ -375,7 +390,8 @@ def draw_right_pane(stdscr, state): line_num = i + state.right_scroll_offset + 1 if (state.show_whole_diff or state.show_deletions) and line_num in deleted_line_map: for del_content in deleted_line_map[line_num]: - display_lines.append({'type': 'deleted', 'content': del_content}) + # For deleted lines, use the same line number + display_lines.append({'type': 'deleted', 'content': del_content, 'line_num': line_num}) is_added = False if state.show_whole_diff or state.show_additions: @@ -384,7 +400,7 @@ def draw_right_pane(stdscr, state): is_added = True break - display_lines.append({'type': 'added' if is_added else 'regular', 'content': line}) + display_lines.append({'type': 'added' if is_added else 'regular', 'content': line, 'line_num': line_num}) display_row = 0 for line_info in display_lines: @@ -393,6 +409,20 @@ def draw_right_pane(stdscr, state): line_type = line_info['type'] content = line_info['content'] + line_num = line_info.get('line_num', 0) + + # Draw line number if enabled + if state.show_line_numbers: + line_num_str = str(line_num).rjust(line_num_width - 1) + " " + line_num_pos = (0 if not state.show_sidebar else state.divider_col + 2) + + # Use different color for line numbers + if line_type == 'added': + stdscr.addstr(display_row, line_num_pos, line_num_str, curses.color_pair(5)) + elif line_type == 'deleted': + stdscr.addstr(display_row, line_num_pos, line_num_str, curses.color_pair(6)) + else: + stdscr.addstr(display_row, line_num_pos, line_num_str, curses.color_pair(7)) # Determine prefix based on line type and diff mode prefix = "" @@ -528,13 +558,14 @@ def draw_status_bars(stdscr, state): if len(parts) >= 4: commit_message = parts[3] - # Add wrap indicator and change position to commit message + # Add indicators to commit message wrap_indicator = "" if state.wrap_lines else "[NW] " + line_num_indicator = "[LN] " if state.show_line_numbers else "" change_indicator = "" if state.change_blocks and state.current_change_idx != -1: change_indicator = f"[Change {state.current_change_idx + 1}/{len(state.change_blocks)}] " - commit_message = wrap_indicator + change_indicator + commit_message + commit_message = wrap_indicator + line_num_indicator + change_indicator + commit_message # Status bar percentages if len(state.file_lines) > 0: @@ -814,6 +845,8 @@ def handle_keyboard_input(key, state: AppState) -> AppState: return toggle_sidebar(state) elif key == ord('w'): return replace(state, wrap_lines=not state.wrap_lines) + elif key == ord('L'): # Capital L to toggle line numbers + return replace(state, show_line_numbers=not state.show_line_numbers) elif key in [110, ord('n')]: # ASCII code for 'n' if state.show_whole_diff or state.show_additions or state.show_deletions: return jump_to_next_change(state) @@ -864,6 +897,9 @@ def main(stdscr, filename, show_diff, show_add, show_del, mouse, wrap_lines=True curses.init_pair(2, curses.COLOR_WHITE, 0) # Inactive pane: white on black curses.init_pair(3, curses.COLOR_GREEN, -1) # Added lines curses.init_pair(4, curses.COLOR_RED, -1) # Deleted lines + curses.init_pair(5, curses.COLOR_GREEN, -1) # Line numbers for added lines + curses.init_pair(6, curses.COLOR_RED, -1) # Line numbers for deleted lines + curses.init_pair(7, curses.COLOR_BLUE, -1) # Regular line numbers height, width = stdscr.getmaxyx() state = AppState( @@ -930,6 +966,7 @@ if __name__ == "__main__": parser.add_argument("--diff-deletions", action="store_true", help="Show deleted lines in red") parser.add_argument("--no-mouse", action="store_true", help="Disable mouse support") parser.add_argument("--no-wrap", action="store_true", help="Disable line wrapping") + parser.add_argument("--line-numbers", action="store_true", help="Show line numbers") parser.add_argument("-v", "--version", action="store_true", help="Show version number") parser.add_argument("filename", nargs="?", help="File to view history for") @@ -956,4 +993,4 @@ if __name__ == "__main__": print(f" git commit -m 'Add {os.path.basename(filename)}'") sys.exit(1) - curses.wrapper(main, filename, args.diff, args.diff_additions, args.diff_deletions, not args.no_mouse, not args.no_wrap) + curses.wrapper(main, filename, args.diff, args.diff_additions, args.diff_deletions, not args.no_mouse, not args.no_wrap, args.line_numbers)