forked from Bananymous/banan-os
				
			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(" "); | 				printf("%s\n", entry.name.data()); | ||||||
| 			const char* format = entries[i].name.sv().contains(' ') ? "'%s%s\e[m'" : "%s%s\e[m"; | 			return ret; | ||||||
| 			printf(format, entry_color(entries[i].st.st_mode), entries[i].name.data()); |  | ||||||
| 		} | 		} | ||||||
| 		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; | 		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