ls: Output in columns
This commit is contained in:
parent
647fedfa19
commit
66d3a1d025
|
@ -8,6 +8,7 @@
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
struct config_t
|
struct config_t
|
||||||
|
@ -37,6 +38,9 @@ struct full_entry_t
|
||||||
BAN::String full_name;
|
BAN::String full_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool g_stdout_terminal { false };
|
||||||
|
winsize g_terminal_size {};
|
||||||
|
|
||||||
const char* entry_color(mode_t mode)
|
const char* entry_color(mode_t mode)
|
||||||
{
|
{
|
||||||
// TODO: handle suid, sgid, sticky
|
// 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));
|
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)
|
int list_directory(const BAN::String& path, config_t config)
|
||||||
{
|
{
|
||||||
static char link_buffer[PATH_MAX];
|
static char link_buffer[PATH_MAX];
|
||||||
|
@ -198,14 +233,59 @@ int list_directory(const BAN::String& path, config_t config)
|
||||||
|
|
||||||
if (!config.list)
|
if (!config.list)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < entries.size(); i++)
|
if (!g_stdout_terminal)
|
||||||
{
|
{
|
||||||
if (i > 0)
|
for (const auto& entry : entries)
|
||||||
|
printf("%s\n", entry.name.data());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(" ");
|
||||||
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());
|
printf(format, entry_color(entries[i].st.st_mode), entries[i].name.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +406,8 @@ int main(int argc, const char* argv[])
|
||||||
else for (; i < argc; i++)
|
else for (; i < argc; i++)
|
||||||
MUST(files.emplace_back(BAN::StringView(argv[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;
|
int ret = 0;
|
||||||
for (size_t i = 0; i < files.size(); i++)
|
for (size_t i = 0; i < files.size(); i++)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue