ls: Output in columns

This commit is contained in:
Bananymous 2025-08-06 21:11:07 +03:00
parent 647fedfa19
commit 66d3a1d025
1 changed files with 88 additions and 6 deletions

View File

@ -8,6 +8,7 @@
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
struct config_t
@ -37,6 +38,9 @@ struct full_entry_t
BAN::String full_name;
};
bool g_stdout_terminal { false };
winsize g_terminal_size {};
const char* entry_color(mode_t mode)
{
// TODO: handle suid, sgid, sticky
@ -116,6 +120,37 @@ BAN::String build_time_string(BAN::Time time)
return MUST(BAN::String::formatted("{2}:{2}", time.hour, time.minute));
}
BAN::Vector<size_t> resolve_column_widths(const BAN::Vector<simple_entry_t>& entries, size_t columns)
{
BAN::Vector<size_t> widths;
MUST(widths.resize(columns));
const size_t rows = BAN::Math::div_round_up(entries.size(), columns);
for (size_t i = 0; i < entries.size(); i++)
widths[i / rows] = BAN::Math::max(widths[i / rows], entries[i].name.size());
size_t full_width = (columns - 1);
for (auto width : widths)
{
if (width == 0)
return {};
full_width += width;
}
if (full_width <= g_terminal_size.ws_col)
return widths;
return {};
}
BAN::Vector<size_t> resolve_layout(const BAN::Vector<simple_entry_t>& entries)
{
for (size_t columns = entries.size(); columns > 1; columns--)
if (auto widths = resolve_column_widths(entries, columns); !widths.empty())
return widths;
return {};
}
int list_directory(const BAN::String& path, config_t config)
{
static char link_buffer[PATH_MAX];
@ -198,14 +233,59 @@ int list_directory(const BAN::String& path, config_t config)
if (!config.list)
{
for (size_t i = 0; i < entries.size(); i++)
if (!g_stdout_terminal)
{
if (i > 0)
printf(" ");
const char* format = entries[i].name.sv().contains(' ') ? "'%s%s\e[m'" : "%s%s\e[m";
printf(format, entry_color(entries[i].st.st_mode), entries[i].name.data());
for (const auto& entry : entries)
printf("%s\n", entry.name.data());
return ret;
}
printf("\n");
bool should_quote = false;
for (size_t i = 0; i < entries.size() && !should_quote; i++)
should_quote = entries[i].name.sv().contains(' ');
if (!should_quote)
for (auto& entry : entries)
MUST(entry.name.push_back(' '));
else
{
for (auto& entry : entries)
{
const char ch = entry.name.sv().contains(' ') ? '\'' : ' ';
MUST(entry.name.insert(ch, 0));
MUST(entry.name.push_back(ch));
}
}
auto layout = resolve_layout(entries);
if (layout.empty())
for (const auto& entry : entries)
printf("%s%s\e[m\n", entry_color(entry.st.st_mode), entry.name.data());
else
{
const size_t cols = layout.size();
const size_t rows = BAN::Math::div_round_up(entries.size(), cols);
for (size_t row = 0; row < rows; row++)
{
for (size_t col = 0; col < cols; col++)
{
const size_t i = col * rows + row;
if (i >= entries.size())
break;
char format[32];
sprintf(format, "%%s%%-%zus\e[m", layout[col]);
if (col != 0)
printf(" ");
printf(format, entry_color(entries[i].st.st_mode), entries[i].name.data());
}
printf("\n");
}
}
return ret;
}
@ -326,6 +406,8 @@ int main(int argc, const char* argv[])
else for (; i < argc; i++)
MUST(files.emplace_back(BAN::StringView(argv[i])));
g_stdout_terminal = isatty(STDOUT_FILENO) && ioctl(STDOUT_FILENO, TIOCGWINSZ, &g_terminal_size) == 0;
int ret = 0;
for (size_t i = 0; i < files.size(); i++)
{