module file_tree_renderer_module use iso_fortran_env, only: int32 use file_tree_module use terminal_io_module implicit none private public :: render_file_tree ! Simple expansion indicators (no box-drawing) character(len=*), parameter :: EXPANDED_DIR = '-' character(len=*), parameter :: COLLAPSED_DIR = '+' character(len=1), parameter :: ESC = achar(27) contains subroutine render_file_tree(state, start_row, end_row, start_col, width, hints_expanded, git_prefix_active) type(tree_state_t), intent(in) :: state integer, intent(in) :: start_row, end_row, start_col, width logical, intent(in) :: hints_expanded logical, intent(in), optional :: git_prefix_active logical :: git_mode integer :: current_row, item_idx, row character(len=512) :: status_line character(len=:), allocatable :: padding ! Handle optional git_prefix_active parameter if (present(git_prefix_active)) then git_mode = git_prefix_active else git_mode = .false. end if ! First, clear all rows in the tree pane padding = repeat(' ', width) do row = start_row, end_row call terminal_move_cursor(row, start_col) call terminal_write(padding) end do current_row = start_row ! Display repo:branch info at top if available if (len_trim(state%repo_name) > 0 .and. len_trim(state%branch_name) > 0) then call terminal_move_cursor(current_row, start_col) write(status_line, '(A,A,A,A,A,A,A)') & ESC // '[1;36m', trim(state%repo_name), ESC // '[0m', & ':', & ESC // '[1;33m', trim(state%branch_name), ESC // '[0m' call terminal_write(trim(status_line)) current_row = current_row + 2 ! Skip a line end if ! Display root if (current_row <= end_row) then call terminal_move_cursor(current_row, start_col) call terminal_write('.') current_row = current_row + 1 end if ! Display tree (leave space for legend - 1 or 4 rows) if (associated(state%root)) then item_idx = 0 if (hints_expanded) then call render_tree_node(state%root, '', .true., & state, item_idx, current_row, end_row - 4, start_col, width) else call render_tree_node(state%root, '', .true., & state, item_idx, current_row, end_row - 1, start_col, width) end if end if ! Display legend at bottom (either minimal or expanded) if (hints_expanded) then ! Expanded legend (four rows) if (end_row >= start_row + 4) then ! First row: navigation call terminal_move_cursor(end_row - 3, start_col) call terminal_write(ESC // '[90m') ! Gray call terminal_write('j/k:nav →:in ←:up o:open .:hide spc:toggle') call terminal_write(ESC // '[0m') ! Second and third rows: git operations (only shown when git mode active) if (git_mode) then ! Git mode active - show git bindings in yellow call terminal_move_cursor(end_row - 2, start_col) call terminal_write(ESC // '[1;33m') ! Bright yellow call terminal_write('a:stage u:unstage d:diff m:commit') call terminal_write(ESC // '[0m') call terminal_move_cursor(end_row - 1, start_col) call terminal_write(ESC // '[1;33m') ! Bright yellow call terminal_write('p:push f:fetch l:pull t:tag esc:cancel') call terminal_write(ESC // '[0m') else ! Normal mode - show shortcuts and fuzzy search hint call terminal_move_cursor(end_row - 2, start_col) call terminal_write(ESC // '[90m') ! Gray call terminal_write('alt-v:vsplit alt-s:hsplit ctrl-g:git') call terminal_write(ESC // '[0m') call terminal_move_cursor(end_row - 1, start_col) call terminal_write(ESC // '[90m') ! Gray call terminal_write('type to fuzzy search files') call terminal_write(ESC // '[0m') end if ! Fourth row: exit call terminal_move_cursor(end_row, start_col) call terminal_write(ESC // '[90m') ! Gray call terminal_write('ctrl-/:collapse esc/F3:close') call terminal_write(ESC // '[0m') end if else ! Minimal legend (one row) if (end_row >= start_row + 1) then call terminal_move_cursor(end_row, start_col) call terminal_write(ESC // '[90m') ! Gray call terminal_write('.:hide ctrl-/:hints esc/F3:close') call terminal_write(ESC // '[0m') end if end if end subroutine render_file_tree recursive subroutine render_tree_node(node, prefix, is_root, & state, item_idx, current_row, end_row, start_col, width) type(tree_node_t), pointer, intent(in) :: node character(len=*), intent(in) :: prefix logical, intent(in) :: is_root type(tree_state_t), intent(in) :: state integer, intent(inout) :: item_idx, current_row integer, intent(in) :: end_row, start_col, width character(len=:), allocatable :: line, new_prefix type(tree_node_t), pointer :: child logical :: is_selected, is_last_child ! Don't print root node if (.not. is_root) then ! Skip hidden files when hide_dotfiles is enabled (dotfiles or gitignored) if (state%hide_dotfiles .and. node%is_file .and. (node%is_dotfile .or. node%is_gitignored)) then return end if ! Increment item_idx for both files and directories (all selectable items) item_idx = item_idx + 1 is_selected = (item_idx == state%selected_index) ! Only render if within viewport and current_row fits if (item_idx >= state%viewport_offset .and. current_row <= end_row) then ! Build line with simple +/- indicators and indentation if (.not. node%is_file) then ! Directory - add expand/collapse indicator and / suffix if (associated(node%first_child)) then ! Directory with children if (node%expanded) then line = prefix // EXPANDED_DIR // ' ' // trim(node%name) // '/' else line = prefix // COLLAPSED_DIR // ' ' // trim(node%name) // '/' end if else ! Empty directory - no expand/collapse indicator line = prefix // ' ' // trim(node%name) // '/' end if else ! File - just indentation and name line = prefix // ' ' // trim(node%name) end if ! Add status indicators for files only if (node%is_file) then if (node%is_staged) then line = line // ' ' // ESC // '[32m↑' // ESC // '[0m' ! Green up arrow end if if (node%is_unstaged) then line = line // ' ' // ESC // '[31m✗' // ESC // '[0m' ! Red X end if if (node%is_untracked) then line = line // ' ' // ESC // '[90m✗' // ESC // '[0m' ! Gray X end if if (node%has_incoming) then line = line // ' ' // ESC // '[34m↓' // ESC // '[0m' ! Blue down arrow end if end if ! Render with selection highlight for files only call terminal_move_cursor(current_row, start_col) if (is_selected) then ! Selection highlight (reverse video) call terminal_write(ESC // '[7m' // line // ESC // '[0m') else ! Grey out directories that only contain hidden files if (.not. node%is_file .and. node%all_children_hidden) then call terminal_write(ESC // '[90m' // line // ESC // '[0m') ! Grey else call terminal_write(line) end if end if current_row = current_row + 1 end if end if ! Render children (only if directory is expanded or root) ! Files don't have children, so skip if is_file. For directories, check if expanded or root. if ((.not. node%is_file .and. node%expanded) .or. is_root) then child => node%first_child do while (associated(child)) ! Determine if this is the last sibling is_last_child = .not. associated(child%next_sibling) ! Build prefix for child based on current node's position if (is_root) then ! Root's children start with no prefix new_prefix = '' else ! Non-root children inherit prefix and add 2-space indentation new_prefix = prefix // ' ' end if call render_tree_node(child, new_prefix, .false., & state, item_idx, current_row, end_row, start_col, width) child => child%next_sibling end do end if end subroutine render_tree_node end module file_tree_renderer_module