Compare commits

..

4 Commits

Author SHA1 Message Date
Patrik Svensson
e280b82679 Fix dividing with infinity bug
When calculating the remaining time for a progress task,
we divide the value delta with the current speed.
If the speed is zero, then the resulting double will
be 'infinity' which will lead to TimeSpan.FromSeconds
throwing.

This commit fixes that bug by setting the speed to 1
if it's 0 when calculating the remaining time.

Closes #169
2020-12-17 00:43:47 +01:00
Patrik Svensson
6932c95731 Fix bug when splitting text containing CJK chars
In Segment.Split, we didn't take cell width into account
when calculating the offset, which resulted in some "fun" bugs.
I've added a new overload for Segment.Split and obsoleted the old one.

Closes #150
2020-12-17 00:02:00 +01:00
Patrik Svensson
ee305702e8 Update issue templates 2020-12-14 10:36:05 +01:00
Patrik Svensson
63abcc92ba Set max value for progress task properly
Also clamp the task value if it's greater than the max value.

Closes #163
2020-12-12 17:29:07 +01:00
10 changed files with 213 additions and 31 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Information**
- OS: [eg Windows/Linux/MacOS]
- Version: [e.g. 0.33.0]
- Terminal: [e.g Windows Terminal]
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,7 @@
┌──────────┐
│ ┌──────┐ │
│ │ 测试 │ │
│ ├──────┤ │
│ │ 测试 │ │
│ └──────┘ │
└──────────┘

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Shouldly;
using Spectre.Console.Rendering;
using VerifyXunit;
using Xunit;
@@ -267,5 +269,23 @@ namespace Spectre.Console.Tests.Unit
// Then
return Verifier.Verify(console.Output);
}
[Fact]
public Task Should_Wrap_Table_With_CJK_Tables_In_Panel_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table();
table.AddColumn("测试");
table.AddRow("测试");
var panel = new Panel(table);
// When
console.Render(panel);
// Then
return Verifier.Verify(console.Output);
}
}
}

View File

@@ -59,7 +59,7 @@ namespace Spectre.Console.Tests.Unit
}
[Fact]
public Task Foo()
public Task Should_Reduce_Width_If_Needed()
{
// Given
var console = new PlainConsole(width: 20);
@@ -87,5 +87,29 @@ namespace Spectre.Console.Tests.Unit
// Then
return Verifier.Verify(console.Output);
}
[Fact]
public void Setting_Max_Value_Should_Set_The_MaxValue_And_Cap_Value()
{
// Given
var task = default(ProgressTask);
var console = new PlainConsole();
var progress = new Progress(console)
.Columns(new[] { new ProgressBarColumn() })
.AutoRefresh(false)
.AutoClear(false);
// When
progress.Start(ctx =>
{
task = ctx.AddTask("foo");
task.Increment(100);
task.MaxValue = 20;
});
// Then
task.MaxValue.ShouldBe(20);
task.Value.ShouldBe(20);
}
}
}

View File

@@ -22,18 +22,43 @@ namespace Spectre.Console.Tests.Unit
[UsesVerify]
public sealed class TheSplitMethod
{
[Fact]
public Task Should_Split_Segment_Correctly()
[Theory]
[InlineData("Foo Bar", 0, "", "Foo Bar")]
[InlineData("Foo Bar", 1, "F", "oo Bar")]
[InlineData("Foo Bar", 2, "Fo", "o Bar")]
[InlineData("Foo Bar", 3, "Foo", " Bar")]
[InlineData("Foo Bar", 4, "Foo ", "Bar")]
[InlineData("Foo Bar", 5, "Foo B", "ar")]
[InlineData("Foo Bar", 6, "Foo Ba", "r")]
[InlineData("Foo Bar", 7, "Foo Bar", null)]
[InlineData("Foo 测试 Bar", 0, "", "Foo 测试 Bar")]
[InlineData("Foo 测试 Bar", 1, "F", "oo 测试 Bar")]
[InlineData("Foo 测试 Bar", 2, "Fo", "o 测试 Bar")]
[InlineData("Foo 测试 Bar", 3, "Foo", " 测试 Bar")]
[InlineData("Foo 测试 Bar", 4, "Foo ", "测试 Bar")]
[InlineData("Foo 测试 Bar", 5, "Foo 测", "试 Bar")]
[InlineData("Foo 测试 Bar", 6, "Foo 测", "试 Bar")]
[InlineData("Foo 测试 Bar", 7, "Foo 测试", " Bar")]
[InlineData("Foo 测试 Bar", 8, "Foo 测试", " Bar")]
[InlineData("Foo 测试 Bar", 9, "Foo 测试 ", "Bar")]
[InlineData("Foo 测试 Bar", 10, "Foo 测试 B", "ar")]
[InlineData("Foo 测试 Bar", 11, "Foo 测试 Ba", "r")]
[InlineData("Foo 测试 Bar", 12, "Foo 测试 Bar", null)]
public void Should_Split_Segment_Correctly(string text, int offset, string expectedFirst, string expectedSecond)
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
var segment = new Segment("Foo Bar", style);
var context = new RenderContext(Encoding.UTF8, false);
var segment = new Segment(text, style);
// When
var result = segment.Split(3);
var (first, second) = segment.Split(context, offset);
// Then
return Verifier.Verify(result);
first.Text.ShouldBe(expectedFirst);
first.Style.ShouldBe(style);
second?.Text?.ShouldBe(expectedSecond);
second?.Style?.ShouldBe(style);
}
}

View File

@@ -8,32 +8,34 @@ namespace Spectre.Console.Internal
{
public static int GetCellLength(RenderContext context, string text)
{
return text.Sum(rune =>
{
if (context.LegacyConsole)
{
// Is it represented by a single byte?
// In that case we don't have to calculate the
// actual cell width.
if (context.Encoding.GetByteCount(new[] { rune }) == 1)
{
return 1;
}
}
return text.Sum(rune => GetCellLength(context, rune));
}
// TODO: We need to figure out why Segment.SplitLines fails
// if we let wcwidth (which returns -1 instead of 1)
// calculate the size for new line characters.
// That is correct from a Unicode perspective, but the
// algorithm was written before wcwidth was added and used
// to work with string length and not cell length.
if (rune == '\n')
public static int GetCellLength(RenderContext context, char rune)
{
if (context.LegacyConsole)
{
// Is it represented by a single byte?
// In that case we don't have to calculate the
// actual cell width.
if (context.Encoding.GetByteCount(new[] { rune }) == 1)
{
return 1;
}
}
return UnicodeCalculator.GetWidth(rune);
});
// TODO: We need to figure out why Segment.SplitLines fails
// if we let wcwidth (which returns -1 instead of 1)
// calculate the size for new line characters.
// That is correct from a Unicode perspective, but the
// algorithm was written before wcwidth was added and used
// to work with string length and not cell length.
if (rune == '\n')
{
return 1;
}
return UnicodeCalculator.GetWidth(rune);
}
}
}

View File

@@ -167,7 +167,7 @@ namespace Spectre.Console
if (maxValue != null)
{
_maxValue += maxValue.Value;
_maxValue = maxValue.Value;
}
if (increment != null)
@@ -175,6 +175,12 @@ namespace Spectre.Console
Value += increment.Value;
}
// Need to cap the max value?
if (Value > _maxValue)
{
Value = _maxValue;
}
var timestamp = DateTime.Now;
var threshold = timestamp - TimeSpan.FromSeconds(30);
@@ -259,6 +265,14 @@ namespace Spectre.Console
return null;
}
// If the speed is zero, the estimate below
// will return infinity (since it's a double),
// so let's set the speed to 1 in that case.
if (speed == 0)
{
speed = 1;
}
var estimate = (MaxValue - Value) / speed.Value;
return TimeSpan.FromSeconds(estimate);
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Spectre.Console.Internal;
namespace Spectre.Console.Rendering
{
@@ -145,6 +146,7 @@ namespace Spectre.Console.Rendering
/// </summary>
/// <param name="offset">The offset where to split the segment.</param>
/// <returns>One or two new segments representing the split.</returns>
[Obsolete("Use Split(RenderContext, Int32) instead")]
public (Segment First, Segment? Second) Split(int offset)
{
if (offset < 0)
@@ -162,6 +164,44 @@ namespace Spectre.Console.Rendering
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
/// <summary>
/// Splits the segment at the offset.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="offset">The offset where to split the segment.</param>
/// <returns>One or two new segments representing the split.</returns>
public (Segment First, Segment? Second) Split(RenderContext context, int offset)
{
if (offset < 0)
{
return (this, null);
}
if (offset >= CellCount(context))
{
return (this, null);
}
var index = 0;
if (offset > 0)
{
var accumulated = 0;
foreach (var character in Text)
{
index++;
accumulated += Cell.GetCellLength(context, character);
if (accumulated >= offset)
{
break;
}
}
}
return (
new Segment(Text.Substring(0, index), Style),
new Segment(Text.Substring(index, Text.Length - index), Style));
}
/// <summary>
/// Clones the segment.
/// </summary>
@@ -219,14 +259,16 @@ namespace Spectre.Console.Rendering
while (stack.Count > 0)
{
var segment = stack.Pop();
var segmentLength = segment.CellCount(context);
// Does this segment make the line exceed the max width?
if (line.CellCount(context) + segment.CellCount(context) > maxWidth)
var lineLength = line.CellCount(context);
if (lineLength + segmentLength > maxWidth)
{
var diff = -(maxWidth - (line.Length + segment.Text.Length));
var diff = -(maxWidth - (lineLength + segmentLength));
var offset = segment.Text.Length - diff;
var (first, second) = segment.Split(offset);
var (first, second) = segment.Split(context, offset);
line.Add(first);
lines.Add(line);