Initial commit
This commit is contained in:
Коммит
a23b083ac5
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-example": {
|
||||
"version": "1.3.1",
|
||||
"commands": [
|
||||
"dotnet-example"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = CRLF
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.sln]
|
||||
indent_style = tab
|
||||
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{xml,config,props,targets,nuspec,ruleset}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
[*.cs]
|
||||
# Sort using and Import directives with System.* appearing first
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_separate_import_directive_groups = false
|
||||
|
||||
# Avoid "this." and "Me." if not necessary
|
||||
dotnet_style_qualification_for_field = false:refactoring
|
||||
dotnet_style_qualification_for_property = false:refactoring
|
||||
dotnet_style_qualification_for_method = false:refactoring
|
||||
dotnet_style_qualification_for_event = false:refactoring
|
||||
|
||||
# Use language keywords instead of framework type names for type references
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# Suggest more modern language features when available
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
|
||||
# Non-private static fields are PascalCase
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||
|
||||
# Non-private readonly fields are PascalCase
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
|
||||
|
||||
# Constants are PascalCase
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
dotnet_naming_style.constant_style.capitalization = pascal_case
|
||||
|
||||
# Instance fields are camelCase and start with _
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Locals and parameters are camelCase
|
||||
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Local functions are PascalCase
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||
|
||||
# By default, name items with PascalCase
|
||||
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
# Newline settings
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Prefer method-like constructs to have a block body
|
||||
csharp_style_expression_bodied_methods = false:none
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_operators = false:none
|
||||
|
||||
# Prefer property-like constructs to have an expression-body
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Suggest more modern language features when available
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = do_not_ignore
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Blocks are allowed
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
# warning RS0037: PublicAPI.txt is missing '#nullable enable'
|
||||
dotnet_diagnostic.RS0037.severity = none
|
|
@ -0,0 +1,93 @@
|
|||
# Misc folders
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Tt]emp/
|
||||
[Pp]ackages/
|
||||
/.artifacts/
|
||||
/[Tt]ools/
|
||||
.idea
|
||||
.DS_Store
|
||||
|
||||
# Cakeup
|
||||
cakeup-x86_64-latest.exe
|
||||
|
||||
# .NET Core CLI
|
||||
/.dotnet/
|
||||
/.packages/
|
||||
dotnet-install.sh*
|
||||
*.lock.json
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
.vscode/
|
||||
launchSettings.json
|
||||
*.sln.ide/
|
||||
|
||||
# Rider
|
||||
src/.idea/**/workspace.xml
|
||||
src/.idea/**/tasks.xml
|
||||
src/.idea/dictionaries
|
||||
src/.idea/**/dataSources/
|
||||
src/.idea/**/dataSources.ids
|
||||
src/.idea/**/dataSources.xml
|
||||
src/.idea/**/dataSources.local.xml
|
||||
src/.idea/**/sqlDataSources.xml
|
||||
src/.idea/**/dynamic.xml
|
||||
src/.idea/**/uiDesigner.xml
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
*.userprefs
|
||||
*.GhostDoc.xml
|
||||
*StyleCop.Cache
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*
|
||||
|
||||
# NCrunch
|
||||
.*crunch*.local.xml
|
||||
_NCrunch_*
|
||||
|
||||
# NuGet Packages Directory
|
||||
packages
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
|
||||
*.received.*
|
||||
|
||||
node_modules
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Patrik Svensson, Phil Scott
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"cake.tool": {
|
||||
"version": "1.1.0",
|
||||
"commands": [
|
||||
"dotnet-cake"
|
||||
]
|
||||
},
|
||||
"gpr": {
|
||||
"version": "0.1.224",
|
||||
"commands": [
|
||||
"gpr"
|
||||
]
|
||||
},
|
||||
"dotnet-example": {
|
||||
"version": "1.3.1",
|
||||
"commands": [
|
||||
"dotnet-example"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ExampleTitle>Ansi</ExampleTitle>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Terminal\Terminal.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using Spectre.Terminal;
|
||||
|
||||
namespace DotNet
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var terminal = new Terminal(new FallbackTerminal());
|
||||
terminal.Output.Write("Hello World!");
|
||||
|
||||
Console.ReadKey();
|
||||
|
||||
terminal.Output.Write("Hello World!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"projects": [ "src" ],
|
||||
"sdk": {
|
||||
"version": "5.0.202",
|
||||
"rollForward": "latestPatch"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<SolutionConfiguration>
|
||||
<Settings>
|
||||
<AllowParallelTestExecution>True</AllowParallelTestExecution>
|
||||
<SolutionConfigured>True</SolutionConfigured>
|
||||
</Settings>
|
||||
</SolutionConfiguration>
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Shouldly;
|
||||
using Spectre.Terminal.Ansi;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Terminal.Tests
|
||||
{
|
||||
public sealed class AnsiSequenceTests
|
||||
{
|
||||
public sealed class TheClearMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Clean_ANSI_Escape_Sequences()
|
||||
{
|
||||
// Given, When
|
||||
var result = AnsiSequence.Clear("\u001b[2KHello \u001b[2BWorld!\u001b[1G!");
|
||||
|
||||
// Then
|
||||
result.ShouldBe("Hello World!!");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheInterpretMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Run_Sequence()
|
||||
{
|
||||
// Given
|
||||
var printer = new AnsiPrinter();
|
||||
var state = new StringBuilder();
|
||||
|
||||
// When
|
||||
AnsiSequence.Interpret(
|
||||
printer, state,
|
||||
"\u001b[2KHello \u001b[2BWorld!\u001b[1G!");
|
||||
|
||||
// Then
|
||||
state.ToString()
|
||||
.ShouldBe("[EL2]Hello [CUD2]World![CHA1]!");
|
||||
}
|
||||
|
||||
private sealed class AnsiPrinter : AnsiSequenceVisitor<StringBuilder>
|
||||
{
|
||||
protected override void CursorDown(CursorDown instruction, StringBuilder context)
|
||||
{
|
||||
context.Append($"[CUD{instruction.Count}]");
|
||||
}
|
||||
|
||||
protected override void CursorHorizontalAbsolute(CursorHorizontalAbsolute instruction, StringBuilder context)
|
||||
{
|
||||
context.Append($"[CHA{instruction.Count}]");
|
||||
}
|
||||
|
||||
protected override void EraseInLine(EraseInLine instruction, StringBuilder context)
|
||||
{
|
||||
context.Append($"[EL{instruction.Mode}]");
|
||||
}
|
||||
|
||||
protected override void PrintText(PrintText instruction, StringBuilder context)
|
||||
{
|
||||
context.Append(instruction.Text.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Terminal\Terminal.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Terminal.Tests
|
||||
{
|
||||
public static class ShouldlyExtensions
|
||||
{
|
||||
public static T And<T>(this T item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.6.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal", "Terminal\Terminal.csproj", "{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Tests", "Terminal.Tests\Terminal.Tests.csproj", "{B9AA6477-1C5F-44CE-87C1-219948DB8772}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{02C78B96-9010-48EC-ADB5-4F48884F8937}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ansi", "..\examples\DotNet\Ansi.csproj", "{23DA1EC0-52D0-4420-828B-2C648E0F63E7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|x64.Build.0 = Release|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{11FF129D-7BA1-4016-A52B-6AAE6C3F7703}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B9AA6477-1C5F-44CE-87C1-219948DB8772}.Release|x86.Build.0 = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{23DA1EC0-52D0-4420-828B-2C648E0F63E7} = {02C78B96-9010-48EC-ADB5-4F48884F8937}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3CAB9C8-0317-476E-AEA8-66EF76BA8661}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,7 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public abstract class AnsiInstruction
|
||||
{
|
||||
public abstract void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public static class AnsiSequence
|
||||
{
|
||||
public static string Clear(string text)
|
||||
{
|
||||
return AnsiSequenceCleaner.Instance.Run(text);
|
||||
}
|
||||
|
||||
public static void Interpret<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context, string text)
|
||||
{
|
||||
if (visitor is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
var instructions = AnsiInstructionParser.Parse(text.AsMemory());
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
instruction.Accept(visitor, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
internal sealed class AnsiSequenceCleaner : AnsiSequenceVisitor<StringBuilder>
|
||||
{
|
||||
internal static AnsiSequenceCleaner Instance { get; } = new AnsiSequenceCleaner();
|
||||
|
||||
public string Run(string text)
|
||||
{
|
||||
var context = new StringBuilder();
|
||||
AnsiSequence.Interpret(Instance, context, text);
|
||||
return context.ToString();
|
||||
}
|
||||
|
||||
protected internal override void PrintText(PrintText instruction, StringBuilder context)
|
||||
{
|
||||
context.Append(instruction.Text);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public abstract class AnsiSequenceVisitor<TContext>
|
||||
{
|
||||
protected internal virtual void CursorBack(CursorBack instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorDown(CursorDown instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorForward(CursorForward instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorHorizontalAbsolute(CursorHorizontalAbsolute instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorNextLine(CursorNextLine instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorPosition(CursorPosition instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorPreviousLine(CursorPreviousLine instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void CursorUp(CursorUp instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void EraseInDisplay(EraseInDisplay instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void EraseInLine(EraseInLine instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void PrintText(PrintText instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void RestoreCursor(RestoreCursor instruction, TContext context)
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void SaveCursor(SaveCursor instruction, TContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorBack : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorBack(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorBack(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorDown : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorDown(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorDown(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorForward : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorForward(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorForward(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorHorizontalAbsolute : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorHorizontalAbsolute(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorHorizontalAbsolute(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorNextLine : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorNextLine(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorNextLine(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorPosition : AnsiInstruction
|
||||
{
|
||||
public int Column { get; }
|
||||
public int Row { get; }
|
||||
|
||||
public CursorPosition(int column, int row)
|
||||
{
|
||||
Column = column;
|
||||
Row = row;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorPosition(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorPreviousLine : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorPreviousLine(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorPreviousLine(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class CursorUp : AnsiInstruction
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public CursorUp(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.CursorUp(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class EraseInDisplay : AnsiInstruction
|
||||
{
|
||||
public int Mode { get; }
|
||||
|
||||
public EraseInDisplay(int mode)
|
||||
{
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.EraseInDisplay(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class EraseInLine : AnsiInstruction
|
||||
{
|
||||
public int Mode { get; }
|
||||
|
||||
public EraseInLine(int mode)
|
||||
{
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.EraseInLine(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class PrintText : AnsiInstruction
|
||||
{
|
||||
public ReadOnlyMemory<char> Text { get; }
|
||||
|
||||
public PrintText(ReadOnlyMemory<char> text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.PrintText(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class RestoreCursor : AnsiInstruction
|
||||
{
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.RestoreCursor(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class SaveCursor : AnsiInstruction
|
||||
{
|
||||
public override void Accept<TContext>(AnsiSequenceVisitor<TContext> visitor, TContext context)
|
||||
{
|
||||
visitor.SaveCursor(this, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
internal sealed class AnsiInstructionParser
|
||||
{
|
||||
public static IEnumerable<AnsiInstruction> Parse(ReadOnlyMemory<char> buffer)
|
||||
{
|
||||
foreach (var token in AnsiInstructionTokenizer.Tokenize(buffer))
|
||||
{
|
||||
if (token.IsText)
|
||||
{
|
||||
yield return new PrintText(token.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
var instruction = ParseInstruction(token.Tokens.ToArray());
|
||||
if (instruction != null)
|
||||
{
|
||||
yield return instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static AnsiInstruction? ParseInstruction(AnsiSequenceToken[] tokens)
|
||||
{
|
||||
if (tokens.Length == 0 || tokens[0].Type != AnsiSequenceTokenType.Csi)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the terminal
|
||||
var terminal = tokens[tokens.Length - 1].AsCharacter();
|
||||
if (terminal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the parameters minus the terminal
|
||||
var parameters = new Span<AnsiSequenceToken>(tokens)[1..^1];
|
||||
|
||||
// Create the instruction
|
||||
return terminal.Value switch
|
||||
{
|
||||
'A' => ParseIntegerInstruction(parameters, count => new CursorUp(count)),
|
||||
'B' => ParseIntegerInstruction(parameters, count => new CursorDown(count)),
|
||||
'C' => ParseIntegerInstruction(parameters, count => new CursorForward(count)),
|
||||
'D' => ParseIntegerInstruction(parameters, count => new CursorBack(count)),
|
||||
'E' => ParseIntegerInstruction(parameters, count => new CursorNextLine(count)),
|
||||
'F' => ParseIntegerInstruction(parameters, count => new CursorPreviousLine(count)),
|
||||
'G' => ParseIntegerInstruction(parameters, count => new CursorHorizontalAbsolute(count)),
|
||||
'J' => ParseIntegerInstruction(parameters, count => new EraseInDisplay(count), defaultValue: 0),
|
||||
'K' => ParseIntegerInstruction(parameters, count => new EraseInLine(count), defaultValue: 0),
|
||||
's' => new SaveCursor(),
|
||||
'u' => new RestoreCursor(),
|
||||
_ => null, // Unknown instruction
|
||||
};
|
||||
}
|
||||
|
||||
private static AnsiInstruction? ParseIntegerInstruction(ReadOnlySpan<AnsiSequenceToken> tokens, Func<int, AnsiInstruction> func, int defaultValue = 1)
|
||||
{
|
||||
if (tokens.Length != 1)
|
||||
{
|
||||
return func(defaultValue);
|
||||
}
|
||||
|
||||
if (tokens[0].Type != AnsiSequenceTokenType.Integer)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return func(int.Parse(tokens[0].Content.Span));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
internal sealed class AnsiInstructionToken
|
||||
{
|
||||
private readonly ReadOnlyMemory<char>? _span;
|
||||
private readonly IReadOnlyList<AnsiSequenceToken>? _tokens;
|
||||
|
||||
public ReadOnlyMemory<char> Span => _span ?? ReadOnlyMemory<char>.Empty;
|
||||
public IReadOnlyList<AnsiSequenceToken> Tokens => _tokens ?? Array.Empty<AnsiSequenceToken>();
|
||||
|
||||
public bool IsText => _span != null;
|
||||
public bool IsAnsiEscapeSequence => _tokens != null;
|
||||
|
||||
private AnsiInstructionToken(ReadOnlyMemory<char>? span, IReadOnlyList<AnsiSequenceToken>? tokens)
|
||||
{
|
||||
_span = span;
|
||||
_tokens = tokens;
|
||||
}
|
||||
|
||||
public static AnsiInstructionToken Text(ReadOnlyMemory<char> span)
|
||||
{
|
||||
return new AnsiInstructionToken(span, null);
|
||||
}
|
||||
|
||||
public static AnsiInstructionToken Sequence(IReadOnlyList<AnsiSequenceToken>? tokens)
|
||||
{
|
||||
return new AnsiInstructionToken(null, tokens);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
internal static class AnsiInstructionTokenizer
|
||||
{
|
||||
public static IReadOnlyList<AnsiInstructionToken> Tokenize(ReadOnlyMemory<char> buffer)
|
||||
{
|
||||
var result = new List<AnsiInstructionToken>();
|
||||
foreach (var (span, isEscapeCode) in AnsiSequenceSplitter.Split(buffer))
|
||||
{
|
||||
if (isEscapeCode)
|
||||
{
|
||||
var tokens = TokenizeEscapeCode(new TextBuffer(span));
|
||||
if (tokens.Count > 0)
|
||||
{
|
||||
result.Add(AnsiInstructionToken.Sequence(tokens));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(AnsiInstructionToken.Text(span));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AnsiSequenceToken> TokenizeEscapeCode(TextBuffer buffer)
|
||||
{
|
||||
var result = new List<AnsiSequenceToken>();
|
||||
while (buffer.CanRead)
|
||||
{
|
||||
if (!ReadEscapeCodeToken(buffer, out var token))
|
||||
{
|
||||
// Could not parse, so return an empty result
|
||||
return Array.Empty<AnsiSequenceToken>();
|
||||
}
|
||||
|
||||
result.Add(token);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool ReadEscapeCodeToken(TextBuffer buffer, [NotNullWhen(true)] out AnsiSequenceToken? token)
|
||||
{
|
||||
var current = buffer.PeekChar();
|
||||
|
||||
// ESC?
|
||||
if (current == 0x1b)
|
||||
{
|
||||
// CSI?
|
||||
var start = buffer.Position;
|
||||
buffer.Discard();
|
||||
if (buffer.CanRead && buffer.PeekChar() == '[')
|
||||
{
|
||||
buffer.Discard();
|
||||
token = new AnsiSequenceToken(AnsiSequenceTokenType.Csi, buffer.Slice(start, buffer.Position));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unknown escape sequence
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char.IsNumber(current))
|
||||
{
|
||||
var start = buffer.Position;
|
||||
while (buffer.CanRead)
|
||||
{
|
||||
current = buffer.PeekChar();
|
||||
if (!char.IsNumber(current))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Discard();
|
||||
}
|
||||
|
||||
var end = buffer.Position;
|
||||
|
||||
token = new AnsiSequenceToken(
|
||||
AnsiSequenceTokenType.Integer,
|
||||
buffer.Slice(start, end));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (char.IsLetter(current))
|
||||
{
|
||||
var start = buffer.Position;
|
||||
buffer.Discard();
|
||||
token = new AnsiSequenceToken(
|
||||
AnsiSequenceTokenType.Character,
|
||||
buffer.Slice(start, start + 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AnsiSequenceToken
|
||||
{
|
||||
public AnsiSequenceTokenType Type { get; }
|
||||
public ReadOnlyMemory<char> Content { get; set; }
|
||||
|
||||
public char? AsCharacter()
|
||||
{
|
||||
return Content.Span[Content.Length - 1];
|
||||
}
|
||||
|
||||
public AnsiSequenceToken(AnsiSequenceTokenType type, ReadOnlyMemory<char> value)
|
||||
{
|
||||
Type = type;
|
||||
Content = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AnsiSequenceTokenType
|
||||
{
|
||||
Unknown,
|
||||
Csi,
|
||||
Character,
|
||||
Integer,
|
||||
Semicolon,
|
||||
QuestionMark,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
internal static class AnsiSequenceSplitter
|
||||
{
|
||||
public static List<(ReadOnlyMemory<char>, bool)> Split(ReadOnlyMemory<char> buffer)
|
||||
{
|
||||
var index = 0;
|
||||
var end = 0;
|
||||
|
||||
var result = new List<(ReadOnlyMemory<char>, bool)>();
|
||||
while (index < buffer.Length)
|
||||
{
|
||||
// Encounter ESC?
|
||||
if (buffer.Span[index] == 0x1b)
|
||||
{
|
||||
var start = index;
|
||||
index++;
|
||||
|
||||
if (index > buffer.Length - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Not CSI?
|
||||
if (buffer.Span[index] != '[')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (index >= buffer.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Any number (including none) of "parameter bytes" in the range 0x30–0x3F
|
||||
if (!EatOptionalRange(buffer, 0x30, 0x3f, ref index))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Any number of "intermediate bytes" in the range 0x20–0x2F
|
||||
if (!EatOptionalRange(buffer, 0x20, 0x2f, ref index))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// A single "final byte" in the range 0x40–0x7E
|
||||
var terminal = buffer.Span[index];
|
||||
if (terminal < 0x40 || terminal > 0x7e)
|
||||
{
|
||||
throw new InvalidOperationException("Malformed ANSI escape code");
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
// Need to flush?
|
||||
if (end < start)
|
||||
{
|
||||
result.Add((buffer[end..start], false));
|
||||
}
|
||||
|
||||
// Add the escape code to the result
|
||||
end = index;
|
||||
result.Add((buffer[start..end], true));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// More to flush?
|
||||
if (end < buffer.Length)
|
||||
{
|
||||
result.Add((buffer[end..buffer.Length], false));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool EatOptionalRange(ReadOnlyMemory<char> buffer, int start, int stop, ref int index)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (index >= buffer.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var current1 = buffer.Span[index];
|
||||
if (current1 < start || current1 > stop)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
|
||||
namespace Spectre.Terminal.Ansi
|
||||
{
|
||||
public sealed class TextBuffer
|
||||
{
|
||||
private readonly ReadOnlyMemory<char> _buffer;
|
||||
private int _position;
|
||||
|
||||
public bool CanRead => _position < _buffer.Length;
|
||||
public int Position => _position;
|
||||
|
||||
public TextBuffer(ReadOnlyMemory<char> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public int Peek()
|
||||
{
|
||||
if (_position >= _buffer.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return _buffer.Span[_position];
|
||||
}
|
||||
|
||||
public char PeekChar()
|
||||
{
|
||||
return (char)Peek();
|
||||
}
|
||||
|
||||
public char ReadChar()
|
||||
{
|
||||
return (char)Read();
|
||||
}
|
||||
|
||||
public void Discard()
|
||||
{
|
||||
Read();
|
||||
}
|
||||
|
||||
public void Discard(char expected)
|
||||
{
|
||||
var read = ReadChar();
|
||||
if (read != expected)
|
||||
{
|
||||
throw new InvalidOperationException($"Expected '{expected}' but got '{read}'.");
|
||||
}
|
||||
}
|
||||
|
||||
public int Read()
|
||||
{
|
||||
var result = Peek();
|
||||
if (result != -1)
|
||||
{
|
||||
_position++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<char> Slice(int start, int stop)
|
||||
{
|
||||
return _buffer.Slice(start, stop - start);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using Spectre.Terminal.Ansi;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
internal sealed class ConsoleAnsiInterpreter : AnsiSequenceVisitor<ConsoleAnsiState>
|
||||
{
|
||||
public static ConsoleAnsiInterpreter Instance { get; } = new ConsoleAnsiInterpreter();
|
||||
|
||||
public void Run(ConsoleAnsiState state, string text)
|
||||
{
|
||||
AnsiSequence.Interpret(this, state, text);
|
||||
}
|
||||
|
||||
protected internal override void EraseInDisplay(EraseInDisplay instruction, ConsoleAnsiState context)
|
||||
{
|
||||
Console.Clear();
|
||||
}
|
||||
|
||||
protected internal override void PrintText(PrintText instruction, ConsoleAnsiState context)
|
||||
{
|
||||
context.Write(instruction.Text.Span);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class ConsoleAnsiState
|
||||
{
|
||||
private readonly Action<string?> _writer;
|
||||
|
||||
public ConsoleAnsiState(Action<string?> writer)
|
||||
{
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<char> text)
|
||||
{
|
||||
_writer(text.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class FallbackTerminal : ITerminalDriver
|
||||
{
|
||||
public TerminalCaps Caps { get; }
|
||||
|
||||
public ITerminalReader Input { get; }
|
||||
public ITerminalWriter Output { get; }
|
||||
public ITerminalWriter Error { get; }
|
||||
|
||||
public FallbackTerminal()
|
||||
{
|
||||
Caps = new TerminalCaps()
|
||||
{
|
||||
Ansi = false,
|
||||
};
|
||||
|
||||
Input = new FallbackTerminalReader();
|
||||
Output = new FallbackTerminalWriter(() => Console.IsOutputRedirected, Console.Out.Write);
|
||||
Error = new FallbackTerminalWriter(() => Console.IsErrorRedirected, Console.Error.Write);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
internal sealed class FallbackTerminalReader : ITerminalReader
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
|
||||
public Encoding Encoding => Console.OutputEncoding;
|
||||
public bool IsRedirected => Console.IsInputRedirected;
|
||||
|
||||
public FallbackTerminalReader()
|
||||
{
|
||||
_stream = Console.OpenStandardInput();
|
||||
}
|
||||
|
||||
public int Read(Span<byte> buffer)
|
||||
{
|
||||
return _stream.Read(buffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
internal sealed class FallbackTerminalWriter : ITerminalWriter
|
||||
{
|
||||
private readonly ConsoleAnsiState _state;
|
||||
private readonly Func<bool> _redirected;
|
||||
|
||||
public Encoding Encoding => Console.InputEncoding;
|
||||
public bool IsRedirected => _redirected();
|
||||
|
||||
public FallbackTerminalWriter(Func<bool> redirected, Action<string?> writer)
|
||||
{
|
||||
_redirected = redirected;
|
||||
_state = new ConsoleAnsiState(writer);
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
var text = Encoding.GetString(buffer);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConsoleAnsiInterpreter.Instance.Run(_state, text);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Spectre.Terminal
|
||||
{
|
||||
public interface ITerminalDriver
|
||||
{
|
||||
TerminalCaps Caps { get; }
|
||||
|
||||
ITerminalReader Input { get; }
|
||||
ITerminalWriter Output { get; }
|
||||
ITerminalWriter Error { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public static class ITerminalWriterExtensions
|
||||
{
|
||||
public static void Write(this ITerminalWriter writer, ReadOnlySpan<char> value)
|
||||
{
|
||||
_ = writer ?? throw new ArgumentNullException(nameof(writer));
|
||||
|
||||
var len = writer.Encoding.GetByteCount(value);
|
||||
var array = ArrayPool<byte>.Shared.Rent(len);
|
||||
|
||||
try
|
||||
{
|
||||
var span = array.AsSpan(0, len);
|
||||
writer.Encoding.GetBytes(value, span);
|
||||
writer.Write(span);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this ITerminalWriter writer, string? value)
|
||||
{
|
||||
Write(writer, value.AsSpan());
|
||||
}
|
||||
|
||||
public static void WriteLine(this ITerminalWriter writer)
|
||||
{
|
||||
Write(writer, Environment.NewLine);
|
||||
}
|
||||
|
||||
public static void WriteLine(this ITerminalWriter writer, string? value)
|
||||
{
|
||||
Write(writer, value.AsSpan());
|
||||
Write(writer, Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public interface ITerminalReader
|
||||
{
|
||||
Encoding Encoding { get; }
|
||||
bool IsRedirected { get; }
|
||||
int Read(Span<byte> buffer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public interface ITerminalWriter
|
||||
{
|
||||
Encoding Encoding { get; }
|
||||
bool IsRedirected { get; }
|
||||
void Write(ReadOnlySpan<byte> buffer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class Terminal
|
||||
{
|
||||
public TerminalInput Input { get; }
|
||||
public TerminalOutput Output { get; }
|
||||
public TerminalOutput Error { get; }
|
||||
|
||||
public Terminal(ITerminalDriver driver)
|
||||
{
|
||||
if (driver is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(driver));
|
||||
}
|
||||
|
||||
Input = new TerminalInput(driver.Input);
|
||||
Output = new TerminalOutput(driver.Output);
|
||||
Error = new TerminalOutput(driver.Error);
|
||||
}
|
||||
|
||||
public static Terminal Create()
|
||||
{
|
||||
// Use the fallback terminal for now
|
||||
return new Terminal(new FallbackTerminal());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,7 @@
|
|||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class TerminalCaps
|
||||
{
|
||||
public bool Ansi { get; init; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class TerminalInput : ITerminalReader
|
||||
{
|
||||
private readonly ITerminalReader _reader;
|
||||
private readonly object _lock;
|
||||
private ITerminalReader? _redirected;
|
||||
|
||||
public Encoding Encoding => GetEncoding();
|
||||
public bool IsRedirected => GetIsRedirected();
|
||||
|
||||
public TerminalInput(ITerminalReader reader)
|
||||
{
|
||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
public void Redirect(ITerminalReader? reader)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_redirected = reader;
|
||||
}
|
||||
}
|
||||
|
||||
public int Read(Span<byte> buffer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
return _redirected.Read(buffer);
|
||||
}
|
||||
|
||||
return _reader.Read(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private Encoding GetEncoding()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
return _redirected.Encoding;
|
||||
}
|
||||
|
||||
return _reader.Encoding;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetIsRedirected()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
return _redirected.IsRedirected;
|
||||
}
|
||||
|
||||
return _reader.IsRedirected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Terminal
|
||||
{
|
||||
public sealed class TerminalOutput : ITerminalWriter
|
||||
{
|
||||
private readonly ITerminalWriter _writer;
|
||||
private readonly object _lock;
|
||||
private ITerminalWriter? _redirected;
|
||||
|
||||
public Encoding Encoding => GetEncoding();
|
||||
public bool IsRedirected => GetIsRedirected();
|
||||
|
||||
public TerminalOutput(ITerminalWriter reader)
|
||||
{
|
||||
_writer = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
public void Redirect(ITerminalWriter? writer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_redirected = writer;
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
_redirected.Write(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_writer.Write(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Encoding GetEncoding()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
return _redirected.Encoding;
|
||||
}
|
||||
|
||||
return _writer.Encoding;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetIsRedirected()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_redirected != null)
|
||||
{
|
||||
return _redirected.IsRedirected;
|
||||
}
|
||||
|
||||
return _writer.IsRedirected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче