| File: Commands\PackageSearch\Table.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.CommandLine.XPlat\NuGet.CommandLine.XPlat.csproj (NuGet.CommandLine.XPlat) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace NuGet.CommandLine.XPlat { internal class Column { public string Header { get; set; } public int Width { get; set; } public bool Highlight { get; set; } } internal class Table { // This is the default window width if we cannot get the actual window width internal const int DefaultWindowWidth = 115; // This is the minimum number of characters in a column which includes "| c |" where c is a character const int MinimumCharactersInAColumn = 4; // This is the list of columns in the table internal readonly List<Column> _columns = new List<Column>(); // This is the list of rows in the table internal List<string[]> _rows = new List<string[]>(); // This is the list of columns to highlight private int[] _columnsToHighlight; // This is the highlighter color private ConsoleColor _highlighter = ConsoleColor.Red; // This is the maximum column width: The maximum number of characters in a column based on the window width private readonly int _maxColumnWidth; // This is the default console color private readonly ConsoleColor _consoleColor = Console.ForegroundColor; public Table(int[] columnsToHighlight, string[] headers, int consoleWidth) { if (consoleWidth <= 0) { throw new ArgumentOutOfRangeException(nameof(consoleWidth), consoleWidth, "Console width must be greater than zero."); } _columnsToHighlight = columnsToHighlight; _maxColumnWidth = Math.Max(MinimumCharactersInAColumn, (consoleWidth - MinimumCharactersInAColumn * headers.Length) / headers.Length); // Add the headers foreach (var header in headers) { _columns.Add(new Column { Header = header, Width = header.Length }); } } /* Add a row to the table * row: The list of values in the row */ public void AddRow(params string[] row) { if (row.Length != _columns.Count) { throw new InvalidOperationException("Row column count does not match header column count."); } for (int i = 0; i < row.Length; i++) { _columns[i].Width = Math.Min(_maxColumnWidth, Math.Max(_columns[i].Width, row[i]?.Length ?? 0)); } _rows.Add(row); } /* Print the table with highlighting * logger: The logger to use for printing * highlightTerm: The term to highlight in the table */ public void PrintResult(string highlightTerm, ILoggerWithColor logger) { if (_rows.Count == 0) { logger.LogMinimal("No results found."); return; } // Print the header PrintRow(logger, _columns.Select(c => c.Header).ToList(), highlightTerm); // Print a separator line PrintRow(logger, _columns.Select(c => "".PadRight(c.Width, '-')).ToList(), ""); foreach (string[] row in _rows) { // Sanitize the values to remove new lines and tabs List<string> sanitizedValues = row.Select(v => SanitizeString(v)).ToList(); PrintRow(logger, sanitizedValues, highlightTerm); // Print a separator line PrintRow(logger, _columns.Select(c => "".PadRight(c.Width, '-')).ToList(), ""); } } private string SanitizeString(string value) { return Regex.Replace(value ?? string.Empty, @"\r\n|\n\r|\n|\r|\t", " "); } /* Print a row in the table * logger: The logger to use for printing * values: The list of values in the row * highlightTerm: The term to highlight in the row */ private void PrintRow(ILoggerWithColor logger, List<string> values, string highlightTerm) { ConsoleColor color = _consoleColor; // In one row there could be multiple rows if the value is too long. subRow is the index of the sub row int subRow = 0; // Keep track of the columns that have been printed List<int> renderedColumns = new List<int>(); bool done = false; List<List<int>> highlight = new List<List<int>>(); // Find the indices of the highlight term in each value foreach (string value in values) { highlight.Add(FindSubstringIndices(value, highlightTerm)); } // Keep printing the row until all the columns have been printed while (!done) { // Print column by column for (int column = 0; column < _columns.Count; column++) { logger.LogInline("| ", color); string value = values[column]; // For each column, print character by character with the appropriate color for (int i = 0; i < _columns[column].Width; i++) { int CharacterIndex = subRow * _columns[column].Width + i; // Change to highlighter color if the character index is within the highlight term if (_columnsToHighlight.Contains(column) && highlight[column].Contains(CharacterIndex)) { color = _highlighter; } // All the characters have been printed if (CharacterIndex >= value.Length) { if (!renderedColumns.Contains(column)) { renderedColumns.Add(column); } logger.LogInline("".PadRight(_columns[column].Width - i), color); break; } // If the character index is within the length of the value, print the character if (CharacterIndex < value.Length) { logger.LogInline(value[CharacterIndex].ToString(), color); } else { logger.LogInline(" ", color); } // If the character index is the last character in the value, add the column to the list of rendered columns if (CharacterIndex == value.Length - 1) { if (!renderedColumns.Contains(column)) { renderedColumns.Add(column); } } // Reset the color to the default color color = _consoleColor; } logger.LogInline(" ", color); } // New line for new row logger.LogInline("|", color); logger.LogMinimal(""); subRow++; // If all the columns have been printed, we are done if (renderedColumns.Count >= values.Count) { done = true; } } } private static List<int> FindSubstringIndices(string str, string substring) { List<int> indices = new List<int>(); if (string.IsNullOrEmpty(substring)) { return indices; } int index = 0; while ((index = str.IndexOf(substring, index, StringComparison.CurrentCultureIgnoreCase)) != -1) { for (int i = 0; i < substring.Length; i++) { indices.Add(index + i); } index += substring.Length; } return indices; } } }