///|
/// Filer view - file tree rendering
// =============================================================================
// Rendering
// =============================================================================
///|
/// Render the filer component
fn render_filer(
state : FilerState,
filer_width : Int,
filer_height : Int,
is_focused : Bool,
) -> TuiNode {
let border_color = if is_focused {
"rgb(100,150,255)"
} else {
"rgb(60,60,60)"
}
let border_char = "\u{2502}"
let content_width = filer_width - 1
let lines : Array[TuiNode] = []
fn make_row(content : String, fg : String) -> TuiNode {
let padded = content +
" ".repeat(content_width - @core.string_display_width(content))
let truncated = truncate_filer_text(padded, content_width)
@vnode.row(min_width=filer_width.to_double(), height=1.0, [
@vnode.text(truncated, fg~),
@vnode.text(border_char, fg=border_color),
])
}
// Header
let dir_name = @ai.js_path_basename(state.current_dir)
let header = if dir_name.length() > 0 { dir_name } else { "/" }
let mode_indicator = if state.git_only_mode { " [G]" } else { "" }
let git_indicator = if state.is_git_repo { "*" } else { "" }
lines.push(
make_row(
" [" + header + "]" + git_indicator + mode_indicator,
"rgb(200,200,100)",
),
)
// Parent directory entry
lines.push(make_row(" ..", "rgb(120,120,120)"))
// Tree items
let visible_height = filer_height - 2
let start_idx = state.scroll_offset
for i = 0; i < visible_height; i = i + 1 {
let item_idx = start_idx + i
if item_idx < state.visible_items.length() {
let item = state.visible_items[item_idx]
let is_selected = item_idx == state.selected_idx && is_focused
// Build prefix with indentation
let indent = " ".repeat(item.depth)
let selector = if is_selected { ">" } else { " " }
let git_char = item.git_status.to_char()
// Directory indicator: [+] collapsed, [-] expanded, or just /
let dir_indicator = if item.is_dir {
if item.is_expanded {
"[-]"
} else {
"[+]"
}
} else {
" "
}
let suffix = if item.is_dir { "/" } else { "" }
// Color
let fg = if is_selected {
"rgb(255,255,100)"
} else {
match item.git_status {
GitStatus::None =>
if item.is_dir {
"rgb(100,180,255)"
} else {
"rgb(180,180,180)"
}
_ => item.git_status.color()
}
}
let display = selector +
git_char +
indent +
dir_indicator +
item.name +
suffix
lines.push(make_row(display, fg))
} else {
lines.push(make_row("", "rgb(60,60,60)"))
}
}
@vnode.column(
min_width=filer_width.to_double(),
height=filer_height.to_double(),
lines,
)
}
// =============================================================================
// Utilities
// =============================================================================
///|
fn truncate_filer_text(text : String, max_width : Int) -> String {
let buf = StringBuilder::new()
let mut current_width = 0
for c in text {
let char_width = @core.char_display_width(c)
if current_width + char_width > max_width {
break
}
buf.write_char(c)
current_width = current_width + char_width
}
buf.to_string()
}