From f2f98b79382fc5eb70011328630ebcb2954723f5 Mon Sep 17 00:00:00 2001 From: Karol Kosek Date: Thu, 14 Sep 2023 16:10:03 +0200 Subject: [PATCH] grep: Hyperlink filenames in tty As the newly created function has been also applied to printing the number of matched file lines, file names will now also be colored with the `--count` option set. :^) --- Base/usr/share/man/man1/grep.md | 3 +- Userland/Utilities/grep.cpp | 95 ++++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/Base/usr/share/man/man1/grep.md b/Base/usr/share/man/man1/grep.md index 836578566f..2cff0400cc 100644 --- a/Base/usr/share/man/man1/grep.md +++ b/Base/usr/share/man/man1/grep.md @@ -5,7 +5,7 @@ grep ## Synopsis ```sh -$ grep [--recursive] [--extended-regexp] [--fixed-strings] [--regexp Pattern] [--file File] [-i] [--line-numbers] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [--count] [file...] +$ grep [--recursive] [--extended-regexp] [--fixed-strings] [--regexp Pattern] [--file File] [-i] [--line-numbers] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [--no-hyperlinks] [--count] [file...] ``` ## Options @@ -24,6 +24,7 @@ $ grep [--recursive] [--extended-regexp] [--fixed-strings] [--regexp Pattern] [- * `-a`, `--text`: Treat binary files as text (same as --binary-mode text) * `-I`: Ignore binary files (same as --binary-mode skip) * `--color WHEN`: When to use colored output for the matching text ([auto], never, always) +* `--no-hyperlinks`: Disable hyperlinks * `-c`, `--count`: Output line count instead of line contents ## Arguments diff --git a/Userland/Utilities/grep.cpp b/Userland/Utilities/grep.cpp index 3814d2879a..b6caa0a23b 100644 --- a/Userland/Utilities/grep.cpp +++ b/Userland/Utilities/grep.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Emanuel Sprung + * Copyright (c) 2023, Karol Kosek * * SPDX-License-Identifier: BSD-2-Clause */ @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +58,61 @@ static DeprecatedString escape_characters(StringView string, StringView characte return builder.to_deprecated_string(); } +static DeprecatedString& hostname() +{ + static DeprecatedString s_hostname; + if (s_hostname.is_empty()) { + auto result = Core::System::gethostname(); + if (result.is_error()) + s_hostname = "localhost"; + else + s_hostname = result.release_value(); + } + return s_hostname; +} + +enum class PrintType { + Path = 1 << 0, + LineNumbers = 1 << 1, +}; +AK_ENUM_BITWISE_OPERATORS(PrintType) + +static void append_formatted_path(StringBuilder& builder, StringView path, Optional line_number, PrintType print_type, bool with_hyperlinks, bool with_color) +{ + if (path == '-') { + path = "(standard input)"sv; + with_hyperlinks = false; + } + + if (with_hyperlinks) { + auto full_path_or_error = FileSystem::real_path(path); + if (!full_path_or_error.is_error()) { + auto fullpath = full_path_or_error.release_value(); + auto url = URL::create_with_file_scheme(fullpath.to_deprecated_string(), {}, hostname()); + if (has_flag(print_type, PrintType::LineNumbers) && line_number.has_value()) + url.set_query(MUST(String::formatted("line_number={}", *line_number))); + builder.appendff("\033]8;;{}\033\\", url.serialize()); + } + } + if (has_flag(print_type, PrintType::Path)) { + if (with_color) + builder.appendff("\033[34m{}\033[39m", path); + else + builder.append(path); + } + if (has_flag(print_type, PrintType::LineNumbers) && line_number.has_value()) { + if (has_flag(print_type, PrintType::Path)) + builder.append(':'); + + if (with_color) + builder.appendff("\033[35m{}\033[39m", *line_number); + else + builder.appendff("{}", *line_number); + } + if (with_hyperlinks) + builder.append("\033]8;;\033\\"sv); +} + ErrorOr serenity_main(Main::Arguments args) { TRY(Core::System::pledge("stdio rpath")); @@ -75,7 +132,9 @@ ErrorOr serenity_main(Main::Arguments args) bool invert_match = false; bool quiet_mode = false; bool suppress_errors = false; - bool colored_output = isatty(STDOUT_FILENO); + bool is_a_tty = isatty(STDOUT_FILENO) == 1; + bool colored_output = is_a_tty; + bool disable_hyperlinks = !is_a_tty; bool count_lines = false; size_t matched_line_count = 0; @@ -145,6 +204,7 @@ ErrorOr serenity_main(Main::Arguments args) return true; }, }); + args_parser.add_option(disable_hyperlinks, "Disable hyperlinks", "no-hyperlinks", 0); args_parser.add_option(count_lines, "Output line count instead of line contents", "count", 'c'); args_parser.add_positional_argument(files, "File(s) to process", "file", Core::ArgsParser::Required::No); args_parser.parse(args); @@ -200,12 +260,21 @@ ErrorOr serenity_main(Main::Arguments args) } if (is_binary && binary_mode == BinaryFileMode::Binary) { - outln(colored_output ? "binary file \x1B[34m{}\x1B[0m matches"sv : "binary file {} matches"sv, filename); + StringBuilder filename_builder; + append_formatted_path(filename_builder, filename, {}, PrintType::Path, !disable_hyperlinks, colored_output); + outln("binary file {} matches"sv, filename_builder.string_view()); } else { - if ((result.matches.size() || invert_match) && print_filename) - out(colored_output ? "\x1B[34m{}:\x1B[0m"sv : "{}:"sv, filename); - if ((result.matches.size() || invert_match) && line_numbers) - out(colored_output ? "\x1B[35m{}:\x1B[0m"sv : "{}:"sv, line_number); + PrintType print_type { 0 }; + if (print_filename) + print_type |= PrintType::Path; + if (line_numbers) + print_type |= PrintType::LineNumbers; + + if ((result.matches.size() || invert_match) && has_any_flag(print_type, PrintType::Path | PrintType::LineNumbers)) { + StringBuilder filename_builder; + append_formatted_path(filename_builder, filename, line_number, print_type, !disable_hyperlinks, colored_output); + out("{}:"sv, filename_builder.string_view()); + } for (auto& match : result.matches) { auto pre_match_length = match.global_offset - last_printed_char_pos; @@ -226,12 +295,10 @@ ErrorOr serenity_main(Main::Arguments args) auto exit_status = ExitStatus::NoLinesMatched; - auto handle_file = [&matches, binary_mode, count_lines, quiet_mode, + auto handle_file = [&matches, binary_mode, count_lines, quiet_mode, disable_hyperlinks, colored_output, &matched_line_count, &exit_status](StringView filename, bool print_filename) -> ErrorOr { auto file = TRY(Core::File::open_file_or_standard_stream(filename, Core::File::OpenMode::Read)); auto buffered_file = TRY(Core::InputBufferedFile::create(move(file))); - if (filename == '-') - filename = "stdin"sv; for (size_t line_number = 1; TRY(buffered_file->can_read_line()); ++line_number) { Array buffer; @@ -249,10 +316,12 @@ ErrorOr serenity_main(Main::Arguments args) } if (count_lines && !quiet_mode) { - if (print_filename) - outln("{}:{}", filename, matched_line_count); - else - outln("{}", matched_line_count); + if (print_filename) { + StringBuilder filename_builder; + append_formatted_path(filename_builder, filename, {}, PrintType::Path, !disable_hyperlinks, colored_output); + out("{}:", filename_builder.string_view()); + } + outln("{}", matched_line_count); matched_line_count = 0; }