Jelajahi Sumber

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. :^)
Karol Kosek 1 tahun lalu
induk
melakukan
f2f98b7938
2 mengubah file dengan 84 tambahan dan 14 penghapusan
  1. 2 1
      Base/usr/share/man/man1/grep.md
  2. 82 13
      Userland/Utilities/grep.cpp

+ 2 - 1
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

+ 82 - 13
Userland/Utilities/grep.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
+ * Copyright (c) 2023, Karol Kosek <krkk@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -9,6 +10,7 @@
 #include <AK/LexicalPath.h>
 #include <AK/ScopeGuard.h>
 #include <AK/StringBuilder.h>
+#include <AK/URL.h>
 #include <AK/Vector.h>
 #include <LibCore/ArgsParser.h>
 #include <LibCore/DirIterator.h>
@@ -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<size_t> 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<int> serenity_main(Main::Arguments args)
 {
     TRY(Core::System::pledge("stdio rpath"));
@@ -75,7 +132,9 @@ ErrorOr<int> 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<int> 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<int> 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<int> 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<void> {
             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<u8, PAGE_SIZE> buffer;
@@ -249,10 +316,12 @@ ErrorOr<int> 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;
             }