mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-17 17:32:50 +08:00
Initial work on TextPath widget
This commit is contained in:
parent
d6cdd1fbda
commit
66fc949e2a
15
examples/Console/Paths/Paths.csproj
Normal file
15
examples/Console/Paths/Paths.csproj
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ExampleTitle>Paths</ExampleTitle>
|
||||||
|
<ExampleDescription>Demonstrates how to write paths.</ExampleDescription>
|
||||||
|
<ExampleGroup>Widgets</ExampleGroup>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
20
examples/Console/Paths/Program.cs
Normal file
20
examples/Console/Paths/Program.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace Live;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Write(new TextPath(@"C:\Users\Patrik\Source\github\patriksvensson-forks\spectre.console\examples\Console\Paths"));
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
|
var table = new Table().BorderColor(Color.Grey);
|
||||||
|
table.AddColumns("[grey]Index[/]", "[yellow]Path[/]");
|
||||||
|
table.AddRow(new Text("1"), new TextPath(@"C:\Users\Patrik\Source\github\patriksvensson-forks\spectre.console\examples\Console\Paths"));
|
||||||
|
AnsiConsole.Write(table);
|
||||||
|
}
|
||||||
|
}
|
@ -67,11 +67,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTa
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimal", "Console\Minimal\Minimal.csproj", "{1780A30A-397A-4CC3-B2A0-A385D9081FA2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimal", "Console\Minimal\Minimal.csproj", "{1780A30A-397A-4CC3-B2A0-A385D9081FA2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlternateScreen", "Console\AlternateScreen\AlternateScreen.csproj", "{8A3B636E-5828-438B-A8F4-83811D2704CD}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AlternateScreen", "Console\AlternateScreen\AlternateScreen.csproj", "{8A3B636E-5828-438B-A8F4-83811D2704CD}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console", "..\src\Spectre.Console\Spectre.Console.csproj", "{0C58FB17-F60A-47AB-84BF-961EC8C06AE6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "..\src\Spectre.Console\Spectre.Console.csproj", "{0C58FB17-F60A-47AB-84BF-961EC8C06AE6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.ImageSharp", "..\src\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.ImageSharp", "..\src\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paths", "Console\Paths\Paths.csproj", "{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -479,6 +481,18 @@ Global
|
|||||||
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x64.Build.0 = Release|Any CPU
|
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x86.ActiveCfg = Release|Any CPU
|
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x86.Build.0 = Release|Any CPU
|
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{65CB00B0-A3AE-4E8F-A990-4C8C1A232FE2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
107
src/Spectre.Console/Widgets/TextPath.cs
Normal file
107
src/Spectre.Console/Widgets/TextPath.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
namespace Spectre.Console;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Representation of a file system path.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TextPath : IRenderable
|
||||||
|
{
|
||||||
|
private readonly string[] _parts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TextPath"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to render.</param>
|
||||||
|
public TextPath(string path)
|
||||||
|
{
|
||||||
|
// Normalize the path
|
||||||
|
path ??= string.Empty;
|
||||||
|
path = path.Replace('\\', '/');
|
||||||
|
path = path.TrimEnd('/').Trim();
|
||||||
|
|
||||||
|
// Get the distinct parts
|
||||||
|
_parts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Measurement Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
var fitted = Fit(context, maxWidth);
|
||||||
|
var length = fitted.Sum(f => f.Length) + fitted.Length - 1;
|
||||||
|
|
||||||
|
return new Measurement(
|
||||||
|
Math.Min(length, maxWidth),
|
||||||
|
Math.Max(length, maxWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
var fitted = Fit(context, maxWidth);
|
||||||
|
|
||||||
|
var parts = new List<Segment>();
|
||||||
|
foreach (var (_, _, last, item) in fitted.Enumerate())
|
||||||
|
{
|
||||||
|
parts.Add(new Segment(item));
|
||||||
|
|
||||||
|
if (!last)
|
||||||
|
{
|
||||||
|
parts.Add(new Segment("/", new Style(Color.Grey)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] Fit(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
// No parts?
|
||||||
|
if (_parts.Length == 0)
|
||||||
|
{
|
||||||
|
return _parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will it fit as is?
|
||||||
|
if (_parts.Sum(p => Cell.GetCellLength(p)) + (_parts.Length - 1) < maxWidth)
|
||||||
|
{
|
||||||
|
return _parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ellipsis = context.Unicode ? "…" : "...";
|
||||||
|
var ellipsisLength = Cell.GetCellLength(ellipsis);
|
||||||
|
|
||||||
|
if (_parts.Length >= 2)
|
||||||
|
{
|
||||||
|
// Try popping parts until it fits
|
||||||
|
var queue = new Queue<string>(_parts.Skip(1).Take(_parts.Length - 2));
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
// Remove the first item
|
||||||
|
queue.Dequeue();
|
||||||
|
|
||||||
|
// Get the current queue width in cells
|
||||||
|
var queueWidth =
|
||||||
|
Cell.GetCellLength(_parts[0]) // First
|
||||||
|
+ ellipsisLength // Ellipsis
|
||||||
|
+ queue.Sum(p => Cell.GetCellLength(p)) // Middle
|
||||||
|
+ Cell.GetCellLength(_parts.Last()) // Last
|
||||||
|
+ queue.Count + 2; // Separators
|
||||||
|
|
||||||
|
// Will it fit?
|
||||||
|
if (maxWidth >= queueWidth)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
result.Add(_parts[0]);
|
||||||
|
result.Add(ellipsis);
|
||||||
|
result.AddRange(queue);
|
||||||
|
result.Add(_parts.Last());
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just trim the last part so it fits
|
||||||
|
var last = _parts.Last();
|
||||||
|
var take = Math.Max(0, maxWidth - ellipsisLength);
|
||||||
|
return new[] { string.Concat(ellipsis, last.Substring(last.Length - take, take)) };
|
||||||
|
}
|
||||||
|
}
|
45
test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs
Normal file
45
test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
namespace Spectre.Console.Tests.Unit;
|
||||||
|
|
||||||
|
public sealed class TextPathTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Full_Path_If_Possible()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new TestConsole().Width(40);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Write(new TextPath("C:/Foo/Bar/Baz.txt"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Output.ShouldBe("C:/Foo/Bar/Baz.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Pop_Segments_From_Left()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new TestConsole().Width(17);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Write(new TextPath("C:/My documents/Bar/Baz.txt"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Output.ShouldBe("C:/…/Bar/Baz.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(8, "1234567890", "…4567890")]
|
||||||
|
[InlineData(9, "1234567890", "…34567890")]
|
||||||
|
public void Should_Use_Last_Segments_If_Less_Than_Three(int width, string input, string expected)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new TestConsole().Width(width);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Write(new TextPath(input));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Output.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user