• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MorganKryze / ConsoleAppVisuals / 8520738714

02 Apr 2024 09:44AM UTC coverage: 96.257%. Remained the same
8520738714

push

github

MorganKryze
📖 (Elements options) update videos

942 of 1051 branches covered (89.63%)

Branch coverage included in aggregate %.

2118 of 2128 relevant lines covered (99.53%)

251.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.86
/src/ConsoleAppVisuals/elements/interactive_elements/TableSelector.cs
1
/*
2
    Copyright (c) 2024 Yann M. Vidamment (MorganKryze)
3
    Licensed under GNU GPL v2.0. See full license at: https://github.com/MorganKryze/ConsoleAppVisuals/blob/main/LICENSE.md
4
*/
5
namespace ConsoleAppVisuals.InteractiveElements;
6

7
/// <summary>
8
/// The <see cref="TableSelector"/> is an interactive element that displays a table with selectable elements.
9
/// </summary>
10
/// <remarks>
11
/// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
12
/// </remarks>
13
/// <value><see cref="TableSelector"/> response type is: <see cref="int"/></value>
14
public class TableSelector : InteractiveElement<int>
15
{
16
    #region Constants
17
    const int DEFAULT_DIMENSIONS = 0;
18
    const string DEFAULT_FOOTER_TEXT = "New";
19
    const string DEFAULT_TITLE = null;
20
    const string DEFAULT_FOOTER = null;
21
    const bool DEFAULT_EXCLUDE_HEADER = true;
22
    const bool DEFAULT_EXCLUDE_FOOTER = true;
23
    const Placement DEFAULT_PLACEMENT = Placement.TopCenter;
24
    const BordersType DEFAULT_BORDERS_TYPE = BordersType.SingleStraight;
25
    #endregion
26

27
    #region Fields
28
    private string? _title;
29
    private List<string>? _rawHeaders;
30
    private List<List<string>>? _rawLines;
31
    private bool _excludeHeader;
32
    private bool _excludeFooter;
33
    private string? _footerText;
34
    private string[]? _displayArray;
35
    private readonly Borders _borders;
36
    private Placement _placement;
37
    #endregion
38

39
    #region Default Properties
40
    /// <summary>
41
    /// Gets the placement of the table on the console.
42
    /// </summary>
43
    public override Placement Placement => _placement;
7✔
44

45
    /// <summary>
46
    /// Gets the height of the table.
47
    /// </summary>
48
    public override int Height => _displayArray?.Length ?? DEFAULT_DIMENSIONS;
2✔
49

50
    /// <summary>
51
    /// Gets the width of the table.
52
    /// </summary>
53
    public override int Width => _displayArray?.Max(x => x.Length) ?? DEFAULT_DIMENSIONS;
15✔
54
    #endregion
55

56
    #region Properties
57
    /// <summary>
58
    /// Gets the title of the table.
59
    /// </summary>
60
    public string? Title => _title;
5✔
61

62
    /// <summary>
63
    /// Gets if the header is excluded.
64
    /// </summary>
65
    public bool ExcludeHeader => _excludeHeader;
4✔
66

67
    /// <summary>
68
    /// Gets if the footer is excluded.
69
    /// </summary>
70
    public bool ExcludeFooter => _excludeFooter;
4✔
71

72
    /// <summary>
73
    /// Gets the text of the footer.
74
    /// </summary>
75
    public string FooterText => _footerText ?? DEFAULT_FOOTER_TEXT;
4✔
76

77
    /// <summary>
78
    /// Gets the borders manager of the table.
79
    /// </summary>
80
    public Borders Borders => _borders;
3,596✔
81

82
    /// <summary>
83
    /// Gets the type of the borders of the table.
84
    /// </summary>
85
    public BordersType BordersType => _borders.Type;
2✔
86

87
    /// <summary>
88
    /// Gets the headers of the table.
89
    /// </summary>
90
    public List<string>? GetRawHeaders => _rawHeaders;
6✔
91

92
    /// <summary>
93
    /// Gets the lines of the table.
94
    /// </summary>
95
    public List<List<string>>? GetRawLines => _rawLines;
7✔
96

97
    /// <summary>
98
    /// Gets the number of lines in the table.
99
    /// </summary>
100
    public int Count => _rawLines?.Count ?? 0;
4✔
101

102
    /// <summary>
103
    /// Gets the display array of the table.
104
    /// </summary>
105
    public string[]? DisplayArray => _displayArray;
2✔
106
    #endregion
107

108
    #region Constructor
109
    /// <summary>
110
    /// The <see cref="TableSelector"/> is an interactive element that displays a table with selectable elements.
111
    /// </summary>
112
    /// <param name="title">The title of the table.</param>
113
    /// <param name="lines">The lines of the table.</param>
114
    /// <param name="headers">The headers of the table.</param>
115
    /// <param name="excludeHeader">Whether to exclude the header from selectable elements.</param>
116
    /// <param name="excludeFooter">Whether to exclude the footer from selectable elements.</param>
117
    /// <param name="footerText">The text to display in the footer.</param>
118
    /// <param name="placement">The placement of the table.</param>
119
    /// <param name="bordersType">The type of the borders of the table.</param>
120
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
121
    /// <exception cref="NullReferenceException">Thrown when no body lines were provided.</exception>
122
    /// <remarks>
123
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
124
    /// </remarks>
125
    /// <value><see cref="TableSelector"/> response type is: <see cref="int"/></value>
126
    public TableSelector(
71✔
127
        string? title = DEFAULT_TITLE,
71✔
128
        List<string>? headers = null,
71✔
129
        List<List<string>>? lines = null,
71✔
130
        bool excludeHeader = DEFAULT_EXCLUDE_HEADER,
71✔
131
        bool excludeFooter = DEFAULT_EXCLUDE_FOOTER,
71✔
132
        string? footerText = DEFAULT_FOOTER,
71✔
133
        Placement placement = DEFAULT_PLACEMENT,
71✔
134
        BordersType bordersType = DEFAULT_BORDERS_TYPE
71✔
135
    )
71✔
136
    {
137
        _title = title;
71✔
138
        _rawHeaders = headers;
71✔
139
        _rawLines = lines;
71✔
140
        _excludeHeader = excludeHeader;
71✔
141
        _excludeFooter = excludeFooter;
71✔
142
        _footerText = footerText;
71✔
143
        _placement = placement;
71✔
144
        _borders = new Borders(bordersType);
71✔
145
        if (CompatibilityCheck())
71✔
146
        {
147
            BuildTable();
49✔
148
        }
149
    }
69✔
150
    #endregion
151

152
    #region Check Methods
153
    private bool CompatibilityCheck()
154
    {
155
        if (_rawHeaders is null)
84✔
156
        {
157
            return CheckRawLines();
16✔
158
        }
159
        else if (_rawLines is null)
68✔
160
        {
161
            return true;
3✔
162
        }
163
        else
164
        {
165
            return CheckRawHeadersAndLines();
65✔
166
        }
167
    }
168

169
    private bool CheckRawLines()
170
    {
171
        if (_rawLines is null || _rawLines.Count == 0)
16✔
172
        {
173
            return false;
11✔
174
        }
175

176
        for (int i = 0; i < _rawLines.Count; i++)
36✔
177
        {
178
            if (_rawLines[i].Count != _rawLines[0].Count)
14✔
179
            {
180
                throw new ArgumentException(
1✔
181
                    "The number of columns in the table is not consistent."
1✔
182
                );
1✔
183
            }
184
        }
185

186
        return true;
4✔
187
    }
188

189
    private bool CheckRawHeadersAndLines()
190
    {
191
        if (_rawLines is null || _rawLines.Count == 0)
65✔
192
        {
193
            return false;
13✔
194
        }
195

196
        for (int i = 0; i < _rawLines.Count; i++)
410✔
197
        {
198
            if (_rawLines[i].Count != _rawHeaders?.Count)
155!
199
            {
200
                throw new ArgumentException(
2✔
201
                    "The number of columns in the table is not consistent(Headers or Lines)."
2✔
202
                );
2✔
203
            }
204
        }
205

206
        return true;
50✔
207
    }
208
    #endregion
209

210
    #region Update Methods
211
    /// <summary>
212
    /// Updates the placement of the table.
213
    /// </summary>
214
    /// <param name="placement">The placement of the table.</param>
215
    /// <remarks>
216
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
217
    /// </remarks>
218
    public void UpdatePlacement(Placement placement)
219
    {
220
        _placement = placement;
2✔
221
        if (CompatibilityCheck())
2✔
222
        {
223
            BuildTable();
2✔
224
        }
225
    }
2✔
226

227
    /// <summary>
228
    /// Updates the type of the borders of the table.
229
    /// </summary>
230
    /// <param name="bordersType">The type of the borders of the table.</param>
231
    /// <remarks>
232
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
233
    /// </remarks>
234
    public void UpdateBordersType(BordersType bordersType)
235
    {
236
        _borders.UpdateBordersType(bordersType);
2✔
237
        if (CompatibilityCheck())
2✔
238
        {
239
            BuildTable();
1✔
240
        }
241
    }
2✔
242

243
    /// <summary>
244
    /// Updates the text of the footer.
245
    /// </summary>
246
    /// <param name="footerText">The text of the footer.</param>
247
    /// <remarks>
248
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
249
    /// </remarks>
250
    public void UpdateFooterText(string footerText)
251
    {
252
        _footerText = footerText;
2✔
253
        if (CompatibilityCheck())
2✔
254
        {
255
            BuildTable();
1✔
256
        }
257
    }
2✔
258

259
    /// <summary>
260
    /// Sets the table to exclude the header.
261
    /// </summary>
262
    /// <param name="excludeHeader">Whether to exclude the header or not.</param>
263
    /// <remarks>
264
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
265
    /// </remarks>
266
    public void SetExcludeHeader(bool excludeHeader = DEFAULT_EXCLUDE_HEADER)
267
    {
268
        _excludeHeader = excludeHeader;
2✔
269
        if (CompatibilityCheck())
2✔
270
        {
271
            BuildTable();
1✔
272
        }
273
    }
2✔
274

275
    /// <summary>
276
    /// Sets the table to exclude the footer.
277
    /// </summary>
278
    /// <param name="excludeFooter">Whether to exclude the footer or not.</param>
279
    /// <remarks>
280
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
281
    /// </remarks>
282
    public void SetExcludeFooter(bool excludeFooter = DEFAULT_EXCLUDE_FOOTER)
283
    {
284
        _excludeFooter = excludeFooter;
2✔
285
        if (CompatibilityCheck())
2✔
286
        {
287
            BuildTable();
1✔
288
        }
289
    }
2✔
290

291
    /// <summary>
292
    /// Adds headers to the table.
293
    /// </summary>
294
    /// <param name="headers">The headers to add.</param>
295
    /// <remarks>
296
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
297
    /// </remarks>
298
    public void AddHeaders(List<string> headers)
299
    {
300
        _rawHeaders = headers;
3✔
301
        if (CompatibilityCheck())
3✔
302
        {
303
            BuildTable();
2✔
304
        }
305
    }
2✔
306

307
    /// <summary>
308
    /// Updates the headers of the table.
309
    /// </summary>
310
    /// <param name="headers">The headers to update.</param>
311
    /// <remarks>
312
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
313
    /// </remarks>
314
    public void UpdateHeaders(List<string> headers)
315
    {
316
        AddHeaders(headers);
1✔
317
    }
1✔
318

319
    /// <summary>
320
    /// Adds a title to the table.
321
    /// </summary>
322
    /// <param name="title">The title to add.</param>
323
    /// <remarks>
324
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
325
    /// </remarks>
326
    public void AddTitle(string title)
327
    {
328
        _title = title;
2✔
329
        BuildTable();
2✔
330
    }
2✔
331

332
    /// <summary>
333
    /// Updates the title of the table.
334
    /// </summary>
335
    /// <param name="title">The title to update.</param>
336
    /// <remarks>
337
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
338
    /// </remarks>
339
    public void UpdateTitle(string title)
340
    {
341
        AddTitle(title);
1✔
342
    }
1✔
343
    #endregion
344

345
    #region Manipulation Methods
346
    /// <summary>
347
    /// Gets the specified line in the table.
348
    /// </summary>
349
    /// <param name="index">The index of the line to return.</param>
350
    /// <returns>The line at the specified index.</returns>
351
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
352
    /// <remarks>
353
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
354
    /// </remarks>
355
    public List<string> GetLine(int index)
356
    {
357
        if (index < 0 || index >= _rawLines?.Count)
4!
358
        {
359
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
3✔
360
        }
361
        return _rawLines![index];
1✔
362
    }
363

364
    /// <summary>
365
    /// Gets all the elements from a column given its index.
366
    /// </summary>
367
    /// <param name="index">The index of the column.</param>
368
    /// <returns>The elements of the column.</returns>
369
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
370
    /// <remarks>
371
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
372
    /// </remarks>
373
    public List<string>? GetColumnData(int index)
374
    {
375
        if (_rawLines is null)
6✔
376
        {
377
            return null;
1✔
378
        }
379

380
        if (index < 0 || index >= _rawLines[0].Count)
5✔
381
        {
382
            throw new ArgumentOutOfRangeException(nameof(index), "Invalid column index.");
3✔
383
        }
384

385
        List<string>? list = new();
2✔
386
        for (int i = 0; i < _rawLines.Count; i++)
16✔
387
        {
388
            list.Add(_rawLines[i][index]);
6✔
389
        }
390
        return list;
2✔
391
    }
392

393
    /// <summary>
394
    /// Gets all the elements from a column given its header.
395
    /// </summary>
396
    /// <param name="header">The header of the column.</param>
397
    /// <returns>The elements of the column.</returns>
398
    /// <exception cref="ArgumentException">Thrown when the header is invalid.</exception>
399
    /// <remarks>
400
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
401
    /// </remarks>
402
    public List<string>? GetColumnData(string header)
403
    {
404
        if (_rawHeaders is null)
4✔
405
        {
406
            return null;
1✔
407
        }
408
        else if (_rawLines is null)
3✔
409
        {
410
            return null;
1✔
411
        }
412
        if (!_rawHeaders.Contains(header))
2✔
413
        {
414
            throw new ArgumentException(nameof(header), "Invalid column header.");
1✔
415
        }
416

417
        return GetColumnData(_rawHeaders.IndexOf(header));
1✔
418
    }
419

420
    /// <summary>
421
    /// Adds a line to the table.
422
    /// </summary>
423
    /// <param name="line">The line to add.</param>
424
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
425
    /// <remarks>
426
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
427
    /// </remarks>
428
    public void AddLine(List<string> line)
429
    {
430
        if (_rawLines?.Count > 0 && line.Count != _rawLines[0].Count)
3!
431
        {
432
            throw new ArgumentException(
1✔
433
                "The number of columns in the table is not consistent with other lines."
1✔
434
            );
1✔
435
        }
436
        if (_rawHeaders is not null && line.Count != _rawHeaders.Count)
2✔
437
        {
438
            throw new ArgumentException(
1✔
439
                "The number of columns in the table is not consistent with the headers."
1✔
440
            );
1✔
441
        }
442
        _rawLines ??= new List<List<string>>();
1!
443
        _rawLines.Add(line);
1✔
444
        BuildTable();
1✔
445
    }
1✔
446

447
    /// <summary>
448
    /// Removes a line from the table given its index.
449
    /// </summary>
450
    /// <param name="index">The index of the line to remove.</param>
451
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
452
    /// <remarks>
453
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
454
    /// </remarks>
455
    public void RemoveLine(int index)
456
    {
457
        if (_rawLines?.Count > 0 && (index < 0 || index >= _rawLines.Count))
4!
458
        {
459
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
460
        }
461

462
        _rawLines?.RemoveAt(index);
2!
463
        BuildTable();
1✔
464
    }
1✔
465

466
    /// <summary>
467
    /// Updates a line in the table.
468
    /// </summary>
469
    /// <param name="index">The index of the line to update.</param>
470
    /// <param name="line">The new line.</param>
471
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
472
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
473
    /// <remarks>
474
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
475
    /// </remarks>
476
    public void UpdateLine(int index, List<string> line)
477
    {
478
        if (_rawLines?.Count > 0)
5!
479
        {
480
            if (index < 0 || index >= _rawLines.Count)
4✔
481
            {
482
                throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
483
            }
484

485
            if (line.Count != _rawHeaders?.Count)
2!
486
            {
487
                throw new ArgumentException(
1✔
488
                    "The number of columns in the table is not consistent."
1✔
489
                );
1✔
490
            }
491
        }
492
        _rawLines![index] = line;
2✔
493
        BuildTable();
1✔
494
    }
1✔
495

496
    /// <summary>
497
    /// Clears the headers of the table.
498
    /// </summary>
499
    /// <remarks>
500
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
501
    /// </remarks>
502
    public void ClearHeaders()
503
    {
504
        _rawHeaders = null;
1✔
505
        BuildTable();
1✔
506
    }
1✔
507

508
    /// <summary>
509
    /// Clears the lines of the table.
510
    /// </summary>
511
    /// <remarks>
512
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
513
    /// </remarks>
514
    public void ClearLines()
515
    {
516
        _rawLines = null;
1✔
517
        BuildTable();
1✔
518
    }
1✔
519

520
    /// <summary>
521
    /// Clears the table.
522
    /// </summary>
523
    /// <remarks>
524
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
525
    /// </remarks>
526
    public void Reset()
527
    {
528
        _title = null;
1✔
529
        _rawHeaders?.Clear();
1!
530
        _rawLines?.Clear();
1!
531
        _displayArray = null;
1✔
532
    }
1✔
533
    #endregion
534

535
    #region Rendering
536

537
    #region Build Methods
538
    private void BuildTable()
539
    {
540
        if (_rawHeaders is null)
64✔
541
        {
542
            if (_rawLines is not null)
5✔
543
            {
544
                BuildLines();
5✔
545
            }
546
        }
547
        else
548
        {
549
            if (_rawLines is null)
59✔
550
            {
551
                BuildHeaders();
4✔
552
            }
553
            else
554
            {
555
                BuildHeadersAndLines();
55✔
556
            }
557
        }
558
    }
55✔
559

560
    private void BuildHeadersAndLines()
561
    {
562
        if (_rawHeaders is not null && _rawLines is not null)
55✔
563
        {
564
            var stringList = new List<string>();
55✔
565
            var localMax = new int[_rawHeaders.Count];
55✔
566
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
567
            {
568
                if (_rawHeaders[i]?.Length > localMax[i])
275!
569
                {
570
                    localMax[i] = _rawHeaders[i]?.Length ?? 0;
275!
571
                }
572
            }
573

574
            for (int i = 0; i < _rawLines.Count; i++)
430✔
575
            {
576
                for (int j = 0; j < _rawLines[i].Count; j++)
1,920✔
577
                {
578
                    if (_rawLines[i][j]?.ToString()?.Length > localMax[j])
800!
579
                    {
580
                        localMax[j] = _rawLines[i][j]?.ToString()?.Length ?? 0;
36!
581
                    }
582
                }
583
            }
584

585
            StringBuilder headerBuilder = new($"{Borders.Vertical} ");
55✔
586
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
587
            {
588
                headerBuilder.Append(_rawHeaders[i]?.PadRight(localMax[i]) ?? "");
275!
589
                if (i != _rawHeaders.Count - 1)
275✔
590
                {
591
                    headerBuilder.Append($" {Borders.Vertical} ");
220✔
592
                }
593
                else
594
                {
595
                    headerBuilder.Append($" {Borders.Vertical}");
55✔
596
                }
597
            }
598
            stringList.Add(headerBuilder.ToString());
55✔
599

600
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
55✔
601
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
602
            {
603
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
604
                upperBorderBuilder.Append(
275✔
605
                    (i != _rawHeaders.Count - 1)
275✔
606
                        ? Borders.Top.ToString()
275✔
607
                        : Borders.TopRight.ToString()
275✔
608
                );
275✔
609
            }
610
            stringList.Insert(0, upperBorderBuilder.ToString());
55✔
611

612
            StringBuilder intermediateBorderBuilder = new(Borders.Left.ToString());
55✔
613
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
614
            {
615
                intermediateBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
616
                intermediateBorderBuilder.Append(
275✔
617
                    (i != _rawHeaders.Count - 1)
275✔
618
                        ? Borders.Cross.ToString()
275✔
619
                        : Borders.Right.ToString()
275✔
620
                );
275✔
621
            }
622
            stringList.Add(intermediateBorderBuilder.ToString());
55✔
623

624
            for (int i = 0; i < _rawLines.Count; i++)
430✔
625
            {
626
                StringBuilder lineBuilder = new($"{Borders.Vertical} ");
160✔
627
                for (int j = 0; j < _rawLines[i].Count; j++)
1,920✔
628
                {
629
                    lineBuilder.Append(_rawLines[i][j]?.ToString()?.PadRight(localMax[j]) ?? "");
800!
630
                    if (j != _rawLines[i].Count - 1)
800✔
631
                    {
632
                        lineBuilder.Append($" {Borders.Vertical} ");
640✔
633
                    }
634
                    else
635
                    {
636
                        lineBuilder.Append($" {Borders.Vertical}");
160✔
637
                    }
638
                }
639
                stringList.Add(lineBuilder.ToString());
160✔
640
            }
641

642
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
55✔
643
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
644
            {
645
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
646
                lowerBorderBuilder.Append(
275✔
647
                    (i != _rawHeaders.Count - 1)
275✔
648
                        ? Borders.Bottom.ToString()
275✔
649
                        : Borders.BottomRight.ToString()
275✔
650
                );
275✔
651
            }
652
            stringList.Add(lowerBorderBuilder.ToString());
55✔
653

654
            _displayArray = stringList.ToArray();
55✔
655
            BuildTitle();
55✔
656
        }
657
    }
55✔
658

659
    private void BuildHeaders()
660
    {
661
        if (_rawHeaders is not null)
4✔
662
        {
663
            var stringList = new List<string>();
4✔
664
            var localMax = new int[_rawHeaders.Count];
4✔
665
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
666
            {
667
                if (_rawHeaders[i]?.Length > localMax[i])
20!
668
                {
669
                    localMax[i] = _rawHeaders[i]?.Length ?? 0;
20!
670
                }
671
            }
672
            StringBuilder headerBuilder = new($"{Borders.Vertical} ");
4✔
673
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
674
            {
675
                headerBuilder.Append(_rawHeaders[i]?.PadRight(localMax[i]) ?? "");
20!
676
                if (i != _rawHeaders.Count - 1)
20✔
677
                {
678
                    headerBuilder.Append($" {Borders.Vertical} ");
16✔
679
                }
680
                else
681
                {
682
                    headerBuilder.Append($" {Borders.Vertical}");
4✔
683
                }
684
            }
685
            stringList.Add(headerBuilder.ToString());
4✔
686
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
4✔
687
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
688
            {
689
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
20✔
690
                upperBorderBuilder.Append(
20✔
691
                    (i != _rawHeaders.Count - 1)
20✔
692
                        ? Borders.Top.ToString()
20✔
693
                        : Borders.TopRight.ToString()
20✔
694
                );
20✔
695
            }
696
            stringList.Insert(0, upperBorderBuilder.ToString());
4✔
697
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
4✔
698
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
699
            {
700
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
20✔
701
                lowerBorderBuilder.Append(
20✔
702
                    (i != _rawHeaders.Count - 1)
20✔
703
                        ? Borders.Bottom.ToString()
20✔
704
                        : Borders.BottomRight.ToString()
20✔
705
                );
20✔
706
            }
707
            stringList.Add(lowerBorderBuilder.ToString());
4✔
708
            _displayArray = stringList.ToArray();
4✔
709
            BuildTitle();
4✔
710
        }
711
    }
4✔
712

713
    private void BuildLines()
714
    {
715
        if (_rawLines is not null)
5✔
716
        {
717
            var stringList = new List<string>();
5✔
718
            var localMax = new int[_rawLines[0].Count];
5✔
719
            for (int i = 0; i < _rawLines.Count; i++)
40✔
720
            {
721
                for (int j = 0; j < _rawLines[i].Count; j++)
180✔
722
                {
723
                    if (_rawLines[i][j]?.ToString()?.Length > localMax[j])
75!
724
                    {
725
                        localMax[j] = _rawLines[i][j]?.ToString()?.Length ?? 0;
35!
726
                    }
727
                }
728
            }
729
            for (int i = 0; i < _rawLines.Count; i++)
40✔
730
            {
731
                StringBuilder line = new($"{Borders.Vertical} ");
15✔
732
                for (int j = 0; j < _rawLines[i].Count; j++)
180✔
733
                {
734
                    line.Append(_rawLines[i][j]?.ToString()?.PadRight(localMax[j]) ?? "");
75!
735
                    if (j != _rawLines[i].Count - 1)
75✔
736
                    {
737
                        line.Append($" {Borders.Vertical} ");
60✔
738
                    }
739
                    else
740
                    {
741
                        line.Append($" {Borders.Vertical}");
15✔
742
                    }
743
                }
744
                stringList.Add(line.ToString());
15✔
745
            }
746
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
5✔
747
            for (int i = 0; i < _rawLines.Count; i++)
40✔
748
            {
749
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
15✔
750
                upperBorderBuilder.Append(
15✔
751
                    (i != _rawLines.Count - 1)
15✔
752
                        ? Borders.Top.ToString()
15✔
753
                        : Borders.TopRight.ToString()
15✔
754
                );
15✔
755
            }
756
            stringList.Insert(0, upperBorderBuilder.ToString());
5✔
757
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
5✔
758
            for (int i = 0; i < _rawLines.Count; i++)
40✔
759
            {
760
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
15✔
761
                lowerBorderBuilder.Append(
15✔
762
                    (i != _rawLines.Count - 1)
15✔
763
                        ? Borders.Bottom.ToString()
15✔
764
                        : Borders.BottomRight.ToString()
15✔
765
                );
15✔
766
            }
767
            stringList.Add(lowerBorderBuilder.ToString());
5✔
768
            _displayArray = stringList.ToArray();
5✔
769
            BuildTitle();
5✔
770
        }
771
    }
5✔
772

773
    private void BuildTitle()
774
    {
775
        if (_title is not null)
64✔
776
        {
777
            var len = _displayArray![0].Length;
31✔
778
            var title = _title.ResizeString(len - 4);
31✔
779
            title = $"{Borders.Vertical} {title} {Borders.Vertical}";
31✔
780
            var upperBorderBuilder = new StringBuilder(Borders.TopLeft.ToString());
31✔
781
            upperBorderBuilder.Append(new string(Borders.Horizontal, len - 2));
31✔
782
            upperBorderBuilder.Append(Borders.TopRight.ToString());
31✔
783
            var display = _displayArray.ToList();
31✔
784
            display[0] = display[0]
31✔
785
                .Remove(0, 1)
31✔
786
                .Insert(0, Borders.Left.ToString())
31✔
787
                .Remove(display[1].Length - 1, 1)
31✔
788
                .Insert(display[1].Length - 1, Borders.Right.ToString());
31✔
789
            display.Insert(0, title);
31✔
790
            display.Insert(0, upperBorderBuilder.ToString());
31✔
791
            _displayArray = display.ToArray();
31✔
792
        }
793
    }
64✔
794
    #endregion
795

796
    /// <summary>
797
    /// Defines the actions to perform when the element is called to be rendered on the console.
798
    /// </summary>
799
    [Visual]
800
    protected override void RenderElementActions()
801
    {
802
        int minIndex = GetMinIndex(_excludeHeader);
803
        int maxIndex = GetMaxIndex(_excludeFooter);
804
        int index = minIndex;
805

806
        while (true)
807
        {
808
            string[] array = new string[_displayArray!.Length];
809
            for (int j = 0; j < _displayArray.Length; j++)
810
            {
811
                array[j] = _displayArray[j];
812
                Core.WritePositionedString(array[j], Placement, false, Line + j);
813
                if (j == index)
814
                {
815
                    Core.WritePositionedString(
816
                        j == _displayArray.Length - 1 ? " " + FooterText + " " : array[j][1..^1],
817
                        Placement,
818
                        true,
819
                        Line + j
820
                    );
821
                }
822
            }
823
            switch (Console.ReadKey(intercept: true).Key)
824
            {
825
                case ConsoleKey.UpArrow:
826
                case ConsoleKey.Z:
827
                    index = HandleUpArrowKey(index, minIndex, maxIndex);
828
                    break;
829
                case ConsoleKey.DownArrow:
830
                case ConsoleKey.S:
831
                    index = HandleDownArrowKey(index, minIndex, maxIndex);
832
                    break;
833
                case ConsoleKey.Enter:
834
                    SendResponse(
835
                        this,
836
                        new InteractionEventArgs<int>(Status.Selected, ReturnIndex(index))
837
                    );
838
                    return;
839
                case ConsoleKey.Escape:
840
                    SendResponse(this, new InteractionEventArgs<int>(Status.Escaped, 0));
841
                    return;
842
                case ConsoleKey.Backspace:
843
                    SendResponse(
844
                        this,
845
                        new InteractionEventArgs<int>(Status.Deleted, ReturnIndex(index))
846
                    );
847
                    return;
848
            }
849
        }
850

851
        [Visual]
852
        int ReturnIndex(int index)
853
        {
854
            return index - GetMinIndex(_excludeHeader);
855
        }
856

857
        [Visual]
858
        int GetMinIndex(bool excludeHeader)
859
        {
860
            if (excludeHeader)
861
            {
862
                return _rawHeaders is null ? 3 : 5;
863
            }
864
            else
865
            {
866
                return 0;
867
            }
868
        }
869

870
        [Visual]
871
        int GetMaxIndex(bool excludeFooter)
872
        {
873
            return excludeFooter ? _displayArray!.Length - 2 : _displayArray!.Length - 1;
874
        }
875

876
        [Visual]
877
        static int HandleUpArrowKey(int index, int minIndex, int maxIndex)
878
        {
879
            if (index == minIndex)
880
            {
881
                return maxIndex;
882
            }
883
            else if (index > minIndex)
884
            {
885
                return index - 1;
886
            }
887
            return index;
888
        }
889

890
        [Visual]
891
        static int HandleDownArrowKey(int index, int minIndex, int maxIndex)
892
        {
893
            if (index == maxIndex)
894
            {
895
                return minIndex;
896
            }
897
            else if (index < maxIndex)
898
            {
899
                return index + 1;
900
            }
901
            return index;
902
        }
903
    }
904

905
    #endregion
906
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc