diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bf15b..fd12fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ All notable changes to this project will be documented in this file. -### [2024.3] 2024-07-05 +## [2024.4] - 2024-07-07 +### Added +- Optionally (-Z) truncate lines if too long. Show `≈` to indicate truncation +- Minor performnce tweaks + +### Fixed +- remove end-of-line padding when no borders specfied #1 +- fix date minute formatting error + +## [2024.3] - 2024-07-05 ### Added - file icons diff --git a/lsv/entry.v b/lsv/entry.v index c5b252b..5d224e3 100644 --- a/lsv/entry.v +++ b/lsv/entry.v @@ -34,10 +34,15 @@ fn get_entries(files []string, options Options) []Entry { for file in files { if os.is_dir(file) { dir_files := os.ls(file) or { continue } - entries << dir_files.map(make_entry(it, file, options)) - continue + entries << match options.all { + true { dir_files.map(make_entry(it, file, options)) } + else { dir_files.filter(!is_dot_file(it)).map(make_entry(it, file, options)) } + } + } else { + if options.all || !is_dot_file(file) { + entries << make_entry(file, '', options) + } } - entries << make_entry(file, '', options) } return entries } @@ -146,3 +151,7 @@ fn checksum(name string, dir_name string, options Options) string { // vfmt on } } + +fn is_dot_file(file string) bool { + return file.len > 0 && file[0] == `.` +} diff --git a/lsv/filter.v b/lsv/filter.v index 17bbbcf..5a523b2 100644 --- a/lsv/filter.v +++ b/lsv/filter.v @@ -1,17 +1,9 @@ fn filter(entries []Entry, options Options) []Entry { - mut filtered := entries.clone() - - if !options.all { - filtered = entries.filter(it.name.starts_with('../') || !it.name.starts_with('.')) + return match true { + // vfmt off + options.only_dirs { entries.clone().filter(it.dir) } + options.only_files { entries.clone().filter(it.file) } + else { entries } + // vfmt on } - - if options.only_dirs { - filtered = filtered.filter(it.dir) - } - - if options.only_files { - filtered = filtered.filter(it.file) - } - - return filtered } diff --git a/lsv/format.v b/lsv/format.v index d6332a2..909ea11 100644 --- a/lsv/format.v +++ b/lsv/format.v @@ -1,8 +1,8 @@ import arrays +import os import strings import term import v.mathutil -import os const cell_max = 12 // limit on wide displays const cell_spacing = 3 // space between cells @@ -12,7 +12,7 @@ enum Align { right } -fn format(entries []Entry, options Options) { +fn print_files(entries []Entry, options Options) { w, _ := term.get_terminal_size() options_width_ok := options.width_in_cols > 0 && options.width_in_cols < 1000 width := if options_width_ok { options.width_in_cols } else { w } @@ -35,7 +35,6 @@ fn format_by_cells(entries []Entry, width int, options Options) { partial_row := entries.len % max_cols != 0 rows := entries.len / max_cols + if partial_row { 1 } else { 0 } max_rows := mathutil.max(1, rows) - mut line := strings.new_builder(200) for r := 0; r < max_rows; r += 1 { for c := 0; c < max_cols; c += 1 { @@ -44,10 +43,10 @@ fn format_by_cells(entries []Entry, width int, options Options) { entry := entries[idx] name := format_entry_name(entry, options) cell := format_cell(name, len, .left, get_style_for(entry, options), options) - line.write_string(cell) + print(cell) } } - println(line) + print_newline() } } @@ -55,19 +54,16 @@ fn format_by_lines(entries []Entry, width int, options Options) { len := entries.max_name_len(options) + cell_spacing cols := mathutil.min(width / len, cell_max) max_cols := mathutil.max(cols, 1) - mut line := strings.new_builder(200) for i, entry in entries { if i % max_cols == 0 && i != 0 { - println(line) + print_newline() } name := format_entry_name(entry, options) cell := format_cell(name, len, .left, get_style_for(entry, options), options) - line.write_string(cell) - } - if entries.len % max_cols != 0 { - println(line) + print(cell) } + print_newline() } fn format_one_per_line(entries []Entry, options Options) { @@ -123,7 +119,7 @@ fn format_table_cell(s string, width int, align Align, style Style, options Opti // surrounds a cell with table borders fn print_dir_name(name string, options Options) { if name.len > 0 { - print('\n') + print_newline() nm := if options.colorize { style_string(name, options.style_di, options) } else { name } println('${nm}:') } @@ -209,3 +205,13 @@ fn format_entry_name(entry Entry, options Options) string { fn real_length(s string) int { return term.strip_ansi(s).runes().len } + +@[inline] +fn print_space() { + print_character(` `) +} + +@[inline] +fn print_newline() { + print_character(`\n`) +} diff --git a/lsv/format_long.v b/lsv/format_long.v index 3abce4b..d89a1e5 100644 --- a/lsv/format_long.v +++ b/lsv/format_long.v @@ -1,5 +1,6 @@ import arrays import os +import term import time import v.mathutil { max } @@ -41,6 +42,7 @@ fn format_long_listing(entries []Entry, options Options) { longest := longest_entries(entries, options) header, cols := format_header(options, longest) header_len := real_length(header) + term_cols, _ := term.get_terminal_size() print_header(header, options, header_len, cols) print_header_border(options, header_len, cols) @@ -53,7 +55,7 @@ fn format_long_listing(entries []Entry, options Options) { if idx % block_size == 0 && idx != 0 { match options.table_format { true { print(border_row_middle(header_len, cols)) } - else { print('\n') } + else { print_newline() } } } } @@ -67,53 +69,53 @@ fn format_long_listing(entries []Entry, options Options) { if options.inode { content := if entry.invalid { unknown } else { entry.stat.inode.str() } print(format_cell(content, longest.inode, Align.right, no_style, options)) - print(space) + print_space() } // checksum if options.checksum != '' { checksum := format_cell(entry.checksum, longest.checksum, .left, dim, options) print(checksum) - print(space) + print_space() } // permissions if !options.no_permissions { flag := file_flag(entry, options) print(format_cell(flag, 1, .left, no_style, options)) - print(space) + print_space() content := permissions(entry, options) print(format_cell(content, permissions_title.len, .right, no_style, options)) - print(space) + print_space() } // octal permissions if options.octal_permissions { content := format_octal_permissions(entry, options) print(format_cell(content, 4, .left, dim, options)) - print(space) + print_space() } // hard links if !options.no_hard_links { content := if entry.invalid { unknown } else { '${entry.stat.nlink}' } print(format_cell(content, longest.nlink, .right, dim, options)) - print(space) + print_space() } // owner name if !options.no_owner_name { content := if entry.invalid { unknown } else { get_owner_name(entry.stat.uid) } print(format_cell(content, longest.owner_name, .right, dim, options)) - print(space) + print_space() } // group name if !options.no_group_name { content := if entry.invalid { unknown } else { get_group_name(entry.stat.gid) } print(format_cell(content, longest.group_name, .right, dim, options)) - print(space) + print_space() } // size @@ -133,31 +135,46 @@ fn format_long_listing(entries []Entry, options Options) { } size := format_cell(content, longest.size, .right, size_style, options) print(size) - print(space) + print_space() } // date/time(modified) if !options.no_date { print(format_time(entry, .modified, options)) - print(space) + print_space() } // date/time (accessed) if options.accessed_date { print(format_time(entry, .accessed, options)) - print(space) + print_space() } // date/time (status change) if options.changed_date { print(format_time(entry, .changed, options)) - print(space) + print_space() } // file name file_name := format_entry_name(entry, options) file_style := get_style_for(entry, options) - println(format_cell(file_name, longest.file, .left, file_style, options)) + match options.table_format { + true { print(format_cell(file_name, longest.file, .left, file_style, options)) } + else { print(format_cell(file_name, 0, .left, file_style, options)) } + } + + // line too long? Print a '≈' in the last column + if options.no_wrap { + mut coord := term.get_cursor_position() or { term.Coord{} } + if coord.x >= term_cols { + coord.x = term_cols + term.set_cursor_position(coord) + print('≈') + } + } + + print_newline() } // bottom border @@ -267,12 +284,12 @@ fn format_header(options Options, longest Longest) (string, []int) { } fn time_format(options Options) string { - return if options.time_iso { - date_iso_format - } else if options.time_compact { - date_compact_format - } else { - date_format + return match true { + // vfmt off + options.time_iso { date_iso_format } + options.time_compact { date_compact_format } + else { date_format } + // vfmt on } } diff --git a/lsv/icons.v b/lsv/icons.v index a938023..1d55ed1 100644 --- a/lsv/icons.v +++ b/lsv/icons.v @@ -169,6 +169,7 @@ const icons_map = { 'ts': '󰛦' 'twig': '\ue61c' 'txt': '\uf15c' + 'v': '𝕍' 'vagrantfile': '\ue21e' 'video': '\uf03d' 'vim': '\ue62b' diff --git a/lsv/lsv.v b/lsv/lsv.v index 4d3cd29..591bc80 100644 --- a/lsv/lsv.v +++ b/lsv/lsv.v @@ -4,6 +4,7 @@ import os fn main() { options := parse_args(os.args) + set_auto_wrap(options) entries := get_entries(options.files, options) mut cyclic := Set[string]{} lsv(entries, options, mut cyclic) @@ -22,12 +23,12 @@ fn lsv(entries []Entry, options Options, mut cyclic Set[string]) { if group_by_dirs.len > 1 || options.recursive { print_dir_name(dir, options) } - format(sorted, options) + print_files(sorted, options) if options.recursive { for entry in sorted { - entry_path := os.join_path(entry.dir_name, entry.name) if entry.dir { + entry_path := os.join_path(entry.dir_name, entry.name) if cyclic.exists(entry_path) { println('===> cyclic reference detected <===') continue @@ -41,3 +42,21 @@ fn lsv(entries []Entry, options Options, mut cyclic Set[string]) { } } } + +fn set_auto_wrap(options Options) { + if options.no_wrap { + wrap_off := '\e[?7l' + wrap_reset := '\e[?7h' + println(wrap_off) + + at_exit(fn [wrap_reset] () { + println(wrap_reset) + }) or {} + + // Ctrl-C handler + os.signal_opt(os.Signal.int, fn (sig os.Signal) { + println('\e[?7h') // until compile bug fixed + exit(0) + }) or {} + } +} diff --git a/lsv/options.v b/lsv/options.v index b500d64..a21522d 100644 --- a/lsv/options.v +++ b/lsv/options.v @@ -21,6 +21,7 @@ struct Options { width_in_cols int with_commas bool icons bool + no_wrap bool // // filter, group and sorting options all bool @@ -74,7 +75,7 @@ fn parse_args(args []string) Options { mut fp := flag.new_flag_parser(args) fp.application(app_name) - fp.version('2024.3') + fp.version('2024.4') fp.skip_executable() fp.description('List information about FILES') fp.arguments_description('[FILES]') @@ -88,6 +89,7 @@ fn parse_args(args []string) Options { recursive := fp.bool('', `R`, false, 'list subdirectories recursively') recursion_depth := fp.int('depth', ` `, max_int, 'limit depth of recursion') list_by_lines := fp.bool('', `X`, false, 'list files by lines instead of by columns') + no_wrap := fp.bool('', `Z`, false, 'do not wrap long lines') one_per_line := fp.bool('', `1`, false, 'list one file per line') width_in_cols := fp.int('width', ` `, 0, 'set output width to \n\nFiltering and Sorting Options:') @@ -159,6 +161,7 @@ fn parse_args(args []string) Options { no_owner_name: no_owner_name no_permissions: no_permissions no_size: no_size + no_wrap: no_wrap octal_permissions: octal_permissions one_per_line: one_per_line only_dirs: only_dirs