Add support for scrolling past the screen end
Also add REPL example
This commit is contained in:
Родитель
88bf000899
Коммит
862978b974
|
@ -7,6 +7,12 @@
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-cake"
|
"dotnet-cake"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"dotnet-example": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-example"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -10,7 +11,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\RadLine\RadLine.csproj" />
|
<ProjectReference Include="..\..\src\RadLine\RadLine.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
|
@ -1,26 +1,26 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace RadLine.Sandbox
|
namespace RadLine.Examples
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static async Task Main()
|
public static async Task Main()
|
||||||
{
|
{
|
||||||
if (!Debugger.IsAttached)
|
if (!LineEditor.IsSupported(AnsiConsole.Console))
|
||||||
{
|
{
|
||||||
Debugger.Launch();
|
AnsiConsole.MarkupLine("The terminal does not support ANSI codes, or it isn't a terminal.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create editor
|
||||||
var editor = new LineEditor()
|
var editor = new LineEditor()
|
||||||
{
|
{
|
||||||
MultiLine = true,
|
MultiLine = true,
|
||||||
Text = "HELLO ABC WORLD DEF GHIJKLMN 🥰 PATRIK WAS HERE",
|
Text = "Hello, and welcome to RadLine!\nPress SHIFT+ENTER to insert a new line\nPress ENTER to submit",
|
||||||
Prompt = new LineNumberPrompt(),
|
Prompt = new LineNumberPrompt(new Style(foreground: Color.Yellow)),
|
||||||
Completion = new TestCompletion(),
|
Completion = new TestCompletion(),
|
||||||
Highlighter = new WordHighlighter()
|
Highlighter = new WordHighlighter()
|
||||||
.AddWord("git", new Style(foreground: Color.Yellow))
|
.AddWord("git", new Style(foreground: Color.Yellow))
|
||||||
|
@ -31,31 +31,29 @@ namespace RadLine.Sandbox
|
||||||
.AddWord("commit", new Style(foreground: Color.Blue))
|
.AddWord("commit", new Style(foreground: Color.Blue))
|
||||||
.AddWord("rebase", new Style(foreground: Color.Red))
|
.AddWord("rebase", new Style(foreground: Color.Red))
|
||||||
.AddWord("Hello", new Style(foreground: Color.Blue))
|
.AddWord("Hello", new Style(foreground: Color.Blue))
|
||||||
.AddWord("Goodbye", new Style(foreground: Color.Green))
|
.AddWord("SHIFT", new Style(foreground: Color.Grey))
|
||||||
.AddWord("World", new Style(foreground: Color.Yellow))
|
.AddWord("ENTER", new Style(foreground: Color.Grey))
|
||||||
.AddWord("Syntax", new Style(decoration: Decoration.Strikethrough))
|
.AddWord("RadLine", new Style(foreground: Color.Yellow, decoration: Decoration.SlowBlink)),
|
||||||
.AddWord("Highlighting", new Style(decoration: Decoration.SlowBlink)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add custom commands
|
// Add custom commands
|
||||||
editor.KeyBindings.Add<PrependSmiley>(ConsoleKey.I, ConsoleModifiers.Control);
|
editor.KeyBindings.Add<InsertSmiley>(ConsoleKey.I, ConsoleModifiers.Control);
|
||||||
|
|
||||||
// Read a line
|
// Read a line (or many)
|
||||||
var result = await editor.ReadLine(CancellationToken.None);
|
var result = await editor.ReadLine(CancellationToken.None);
|
||||||
|
|
||||||
// Write the buffer
|
// Write the buffer
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Panel(result.EscapeMarkup())
|
AnsiConsole.Render(new Panel(result.EscapeMarkup())
|
||||||
.Header("[yellow]Commit details:[/]")
|
.Header("[yellow]Text:[/]")
|
||||||
.RoundedBorder());
|
.RoundedBorder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PrependSmiley : LineEditorCommand
|
public sealed class InsertSmiley : LineEditorCommand
|
||||||
{
|
{
|
||||||
public override void Execute(LineEditorContext context)
|
public override void Execute(LineEditorContext context)
|
||||||
{
|
{
|
||||||
context.Execute(new PreviousWordCommand());
|
|
||||||
context.Buffer.Insert(":-)");
|
context.Buffer.Insert(":-)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +69,7 @@ namespace RadLine.Sandbox
|
||||||
|
|
||||||
if (context.Equals("git ", StringComparison.Ordinal))
|
if (context.Equals("git ", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return new[] { "init", "initialize", "push", "commit", "rebase" };
|
return new[] { "init", "branch", "push", "commit", "rebase" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
|
@ -0,0 +1,3 @@
|
||||||
|
root = false
|
||||||
|
|
||||||
|
[*.cs]
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace RadLine.Examples
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static async Task Main()
|
||||||
|
{
|
||||||
|
if (!LineEditor.IsSupported(AnsiConsole.Console))
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("The terminal does not support ANSI codes, or it isn't a terminal.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Render(new FigletText("BASIC REPL"));
|
||||||
|
|
||||||
|
// Create editor
|
||||||
|
var editor = new LineEditor()
|
||||||
|
{
|
||||||
|
MultiLine = true,
|
||||||
|
Text = "PRINT \"Hello\"",
|
||||||
|
Prompt = new LineNumberPrompt(new Style(foreground: Color.Yellow)),
|
||||||
|
Highlighter = new WordHighlighter()
|
||||||
|
.AddWord("$", new Style(foreground: Color.Yellow))
|
||||||
|
.AddWord("INPUT", new Style(foreground: Color.Blue))
|
||||||
|
.AddWord("PRINT", new Style(foreground: Color.Blue)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add custom commands
|
||||||
|
editor.KeyBindings.Clear();
|
||||||
|
editor.KeyBindings.Add<BackspaceCommand>(ConsoleKey.Backspace);
|
||||||
|
editor.KeyBindings.Add<DeleteCommand>(ConsoleKey.Delete);
|
||||||
|
editor.KeyBindings.Add<MoveHomeCommand>(ConsoleKey.Home);
|
||||||
|
editor.KeyBindings.Add<MoveEndCommand>(ConsoleKey.End);
|
||||||
|
editor.KeyBindings.Add<MoveFirstLineCommand>(ConsoleKey.PageUp);
|
||||||
|
editor.KeyBindings.Add<MoveLastLineCommand>(ConsoleKey.PageDown);
|
||||||
|
editor.KeyBindings.Add<MoveLeftCommand>(ConsoleKey.LeftArrow);
|
||||||
|
editor.KeyBindings.Add<MoveRightCommand>(ConsoleKey.RightArrow);
|
||||||
|
editor.KeyBindings.Add<PreviousWordCommand>(ConsoleKey.LeftArrow, ConsoleModifiers.Control);
|
||||||
|
editor.KeyBindings.Add<NextWordCommand>(ConsoleKey.RightArrow, ConsoleModifiers.Control);
|
||||||
|
editor.KeyBindings.Add<SubmitCommand>(ConsoleKey.Enter);
|
||||||
|
editor.KeyBindings.Add<NewLineCommand>(ConsoleKey.Enter, ConsoleModifiers.Shift);
|
||||||
|
|
||||||
|
// Read a line (or many)
|
||||||
|
var result = await editor.ReadLine(CancellationToken.None);
|
||||||
|
|
||||||
|
// Write the buffer
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Panel(result.EscapeMarkup())
|
||||||
|
.Header("[yellow]Program:[/]")
|
||||||
|
.RoundedBorder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\RadLine\RadLine.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -1,8 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace RadLine.Tests.Utilities
|
namespace RadLine.Tests.Utilities
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,11 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 16.6.30114.105
|
VisualStudioVersion = 16.6.30114.105
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadLine", "RadLine\RadLine.csproj", "{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadLine", "RadLine\RadLine.csproj", "{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadLine.Tests", "RadLine.Tests\RadLine.Tests.csproj", "{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadLine.Tests", "RadLine.Tests\RadLine.Tests.csproj", "{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadLine.Sandbox", "RadLine.Sandbox\RadLine.Sandbox.csproj", "{0DEC2184-5587-49C0-80FF-A963E6F494A9}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{4BC15770-BCF4-443D-B3BB-2DD0936D9B15}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "..\examples\Demo\Demo.csproj", "{D4041FF2-FA9D-499C-8523-E5461983A503}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "..\examples\Repl\Repl.csproj", "{B9E9618E-CB8F-4441-8D36-D96D01F73311}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -18,9 +22,6 @@ Global
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DFF3DD16-6AE7-4FDB-807F-F2E8A3166691}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
@ -46,17 +47,39 @@ Global
|
||||||
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x64.Build.0 = Release|Any CPU
|
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x86.ActiveCfg = Release|Any CPU
|
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x86.Build.0 = Release|Any CPU
|
{5DFDEEA4-7B61-47FD-AEA0-14FB569EBE62}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|x64.Build.0 = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Debug|x86.Build.0 = Debug|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|x64.ActiveCfg = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|x64.Build.0 = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|x86.ActiveCfg = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{0DEC2184-5587-49C0-80FF-A963E6F494A9}.Release|x86.Build.0 = Release|Any CPU
|
{D4041FF2-FA9D-499C-8523-E5461983A503}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{D4041FF2-FA9D-499C-8523-E5461983A503} = {4BC15770-BCF4-443D-B3BB-2DD0936D9B15}
|
||||||
|
{B9E9618E-CB8F-4441-8D36-D96D01F73311} = {4BC15770-BCF4-443D-B3BB-2DD0936D9B15}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {704BD8C0-DF3C-41CA-84B7-5A2634CB9129}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -4,6 +4,6 @@ namespace RadLine
|
||||||
{
|
{
|
||||||
public interface ILineEditorPrompt
|
public interface ILineEditorPrompt
|
||||||
{
|
{
|
||||||
(Markup Markup, int Margin) GetPrompt(int line);
|
(Markup Markup, int Margin) GetPrompt(ILineEditorState state, int line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace RadLine
|
||||||
|
{
|
||||||
|
public interface ILineEditorState
|
||||||
|
{
|
||||||
|
public int LineIndex { get; }
|
||||||
|
public int LineCount { get; }
|
||||||
|
public bool IsFirstLine { get; }
|
||||||
|
public bool IsLastLine { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace RadLine
|
namespace RadLine
|
||||||
{
|
{
|
||||||
|
@ -8,23 +6,4 @@ namespace RadLine
|
||||||
{
|
{
|
||||||
public IEnumerable<string>? GetCompletions(string prefix, string word, string suffix);
|
public IEnumerable<string>? GetCompletions(string prefix, string word, string suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TextCompletionExtensions
|
|
||||||
{
|
|
||||||
public static bool TryGetCompletions(
|
|
||||||
this ITextCompletion completion,
|
|
||||||
string prefix, string word, string suffix,
|
|
||||||
[NotNullWhen(true)] out string[]? result)
|
|
||||||
{
|
|
||||||
var completions = completion.GetCompletions(prefix, word, suffix);
|
|
||||||
if (completions == null || !completions.Any())
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = completions.ToArray();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace RadLine
|
||||||
|
{
|
||||||
|
public static class ITextCompletionExtensions
|
||||||
|
{
|
||||||
|
public static bool TryGetCompletions(
|
||||||
|
this ITextCompletion completion,
|
||||||
|
string prefix, string word, string suffix,
|
||||||
|
[NotNullWhen(true)] out string[]? result)
|
||||||
|
{
|
||||||
|
var completions = completion.GetCompletions(prefix, word, suffix);
|
||||||
|
if (completions == null || !completions.Any())
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = completions.ToArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace RadLine
|
namespace RadLine
|
||||||
{
|
{
|
||||||
internal static class IntExtensions
|
internal static class IntExtensions
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace RadLine
|
||||||
|
{
|
||||||
|
internal static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string NormalizeNewLines(this string? text)
|
||||||
|
{
|
||||||
|
text = text?.Replace("\r\n", "\n");
|
||||||
|
text ??= string.Empty;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,17 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Advanced;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace RadLine
|
namespace RadLine
|
||||||
{
|
{
|
||||||
internal sealed class LineBufferRenderer
|
internal sealed class LineBufferRenderer
|
||||||
{
|
{
|
||||||
private readonly IAnsiConsole _console;
|
private readonly IAnsiConsole _console;
|
||||||
private readonly AnsiRenderingStrategy _ansiRendererer;
|
private readonly IHighlighterAccessor _accessor;
|
||||||
private readonly FallbackRenderingStrategy _fallbackRender;
|
private bool _initialized;
|
||||||
|
|
||||||
public LineBufferRenderer(IAnsiConsole console, IHighlighterAccessor accessor)
|
public LineBufferRenderer(IAnsiConsole console, IHighlighterAccessor accessor)
|
||||||
{
|
{
|
||||||
|
@ -17,20 +21,213 @@ namespace RadLine
|
||||||
}
|
}
|
||||||
|
|
||||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
_ansiRendererer = new AnsiRenderingStrategy(console, accessor);
|
_accessor = accessor;
|
||||||
_fallbackRender = new FallbackRenderingStrategy(console, accessor);
|
}
|
||||||
|
|
||||||
|
public void Refresh(LineEditorState state)
|
||||||
|
{
|
||||||
|
if (!_console.Profile.Capabilities.Ansi)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Terminal does not support ANSI");
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// First render?
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
Initialize(state, builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cursor to home position
|
||||||
|
builder.Append("\u001b[1;1H");
|
||||||
|
|
||||||
|
var rowIndex = 0;
|
||||||
|
var height = _console.Profile.Height;
|
||||||
|
var lineCount = state.LineCount - 1;
|
||||||
|
var middleOfList = height / 2;
|
||||||
|
var offset = (height % 2 == 0) ? 1 : 0;
|
||||||
|
var pointer = state.LineIndex;
|
||||||
|
|
||||||
|
// Calculate the visible part
|
||||||
|
var scrollable = lineCount >= height;
|
||||||
|
if (scrollable)
|
||||||
|
{
|
||||||
|
var skip = Math.Max(0, state.LineIndex - middleOfList);
|
||||||
|
|
||||||
|
if (lineCount - state.LineIndex < middleOfList)
|
||||||
|
{
|
||||||
|
// Pointer should be below the end of the list
|
||||||
|
var diff = middleOfList - (lineCount - state.LineIndex);
|
||||||
|
skip -= diff - offset;
|
||||||
|
pointer = middleOfList + diff - offset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Take skip into account
|
||||||
|
pointer -= skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowIndex = skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render all lines
|
||||||
|
for (var i = 0; i < Math.Min(state.LineCount, height); i++)
|
||||||
|
{
|
||||||
|
// Set cursor to beginning of line
|
||||||
|
builder.Append("\u001b[1G");
|
||||||
|
|
||||||
|
// Render the line
|
||||||
|
var (prompt, margin) = state.Prompt.GetPrompt(state, rowIndex);
|
||||||
|
AppendLine(builder, state.GetBufferAt(rowIndex), prompt, margin, 0);
|
||||||
|
|
||||||
|
// Move cursor down
|
||||||
|
builder.Append("\u001b[1E");
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position the cursor at the right line
|
||||||
|
builder.Append("\u001b[").Append(pointer + 1).Append(";1H");
|
||||||
|
|
||||||
|
// Flush
|
||||||
|
_console.WriteAnsi(builder.ToString());
|
||||||
|
|
||||||
|
// Refresh the current line
|
||||||
|
RenderLine(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize(LineEditorState state, StringBuilder builder)
|
||||||
|
{
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
// Everything fit inside the terminal?
|
||||||
|
if (state.LineCount < _console.Profile.Height)
|
||||||
|
{
|
||||||
|
_console.Cursor.Hide();
|
||||||
|
|
||||||
|
for (var i = 0; i < state.LineCount; i++)
|
||||||
|
{
|
||||||
|
var (prompt, margin) = state.Prompt.GetPrompt(state, i);
|
||||||
|
AppendLine(builder, state.GetBufferAt(i), prompt, margin, 0);
|
||||||
|
|
||||||
|
if (i != state.LineCount - 1)
|
||||||
|
{
|
||||||
|
builder.Append(Environment.NewLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_console.WriteAnsi(builder.ToString());
|
||||||
|
_console.Cursor.Show();
|
||||||
|
|
||||||
|
// Move to the last line
|
||||||
|
state.Move(state.LineCount);
|
||||||
|
RenderLine(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RenderLine(LineEditorState state, int? cursorPosition = null)
|
public void RenderLine(LineEditorState state, int? cursorPosition = null)
|
||||||
{
|
{
|
||||||
if (_console.Profile.Capabilities.Ansi)
|
if (!_console.Profile.Capabilities.Ansi)
|
||||||
{
|
{
|
||||||
_ansiRendererer.Render(state, cursorPosition);
|
throw new NotSupportedException("Terminal does not support ANSI");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Prepare
|
||||||
|
builder.Append("\u001b[?7l"); // Autowrap off
|
||||||
|
builder.Append("\u001b[2K"); // Clear the current line
|
||||||
|
builder.Append("\u001b[1G"); // Set cursor to beginning of line
|
||||||
|
|
||||||
|
// Append the whole line
|
||||||
|
var (prompt, margin) = state.Prompt.GetPrompt(state, state.LineIndex);
|
||||||
|
var position = AppendLine(builder, state.Buffer, prompt, margin, cursorPosition ?? state.Buffer.CursorPosition);
|
||||||
|
|
||||||
|
// Move the cursor to the right position
|
||||||
|
var cursorPos = position + prompt.Length + margin + 1;
|
||||||
|
builder.Append("\u001b[").Append(cursorPos).Append('G');
|
||||||
|
|
||||||
|
// Flush
|
||||||
|
_console.WriteAnsi(builder.ToString());
|
||||||
|
|
||||||
|
// Turn on auto wrap
|
||||||
|
builder.Append("\u001b[?7h");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? AppendLine(StringBuilder builder, LineBuffer buffer, Markup prompt, int margin, int cursorPosition)
|
||||||
|
{
|
||||||
|
// Render the prompt
|
||||||
|
builder.Append(_console.ToAnsi(prompt));
|
||||||
|
builder.Append(new string(' ', margin));
|
||||||
|
|
||||||
|
// Build the buffer
|
||||||
|
var width = _console.Profile.Width - prompt.Length - margin - 1;
|
||||||
|
var (content, position) = BuildLine(buffer, width, cursorPosition);
|
||||||
|
|
||||||
|
var output = _console.ToAnsi(Highlight(content));
|
||||||
|
if (output.Length < width)
|
||||||
{
|
{
|
||||||
_fallbackRender.Render(state, cursorPosition);
|
output = output.PadRight(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output the buffer
|
||||||
|
builder.Append(output);
|
||||||
|
|
||||||
|
// Return the position
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRenderable Highlight(string text)
|
||||||
|
{
|
||||||
|
var highlighter = _accessor.Highlighter;
|
||||||
|
if (highlighter == null)
|
||||||
|
{
|
||||||
|
return new Text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
var paragraph = new Paragraph();
|
||||||
|
foreach (var token in StringTokenizer.Tokenize(text))
|
||||||
|
{
|
||||||
|
var style = string.IsNullOrWhiteSpace(token) ? null : highlighter.Highlight(token);
|
||||||
|
paragraph.Append(token, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paragraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string Content, int? Cursor) BuildLine(LineBuffer buffer, int width, int position)
|
||||||
|
{
|
||||||
|
var middleOfList = width / 2;
|
||||||
|
|
||||||
|
var skip = 0;
|
||||||
|
var take = buffer.Content.Length;
|
||||||
|
var pointer = position;
|
||||||
|
|
||||||
|
var scrollable = buffer.Content.Length > width;
|
||||||
|
if (scrollable)
|
||||||
|
{
|
||||||
|
skip = Math.Max(0, position - middleOfList);
|
||||||
|
take = Math.Min(width, buffer.Content.Length - skip);
|
||||||
|
|
||||||
|
if (buffer.Content.Length - position < middleOfList)
|
||||||
|
{
|
||||||
|
// Pointer should be below the end of the list
|
||||||
|
var diff = middleOfList - (buffer.Content.Length - position);
|
||||||
|
skip -= diff;
|
||||||
|
take += diff;
|
||||||
|
pointer = middleOfList + diff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Take skip into account
|
||||||
|
pointer -= skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
string.Concat(buffer.Content.Skip(skip).Take(take)),
|
||||||
|
pointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,25 +4,49 @@ using System.Linq;
|
||||||
|
|
||||||
namespace RadLine
|
namespace RadLine
|
||||||
{
|
{
|
||||||
internal sealed class LineEditorState
|
internal sealed class LineEditorState : ILineEditorState
|
||||||
{
|
{
|
||||||
private readonly List<LineBuffer> _lines;
|
private readonly List<LineBuffer> _lines;
|
||||||
private readonly ILineEditorPrompt _prompt;
|
|
||||||
private int _lineIndex;
|
private int _lineIndex;
|
||||||
|
|
||||||
|
public ILineEditorPrompt Prompt { get; }
|
||||||
|
|
||||||
public int LineIndex => _lineIndex;
|
public int LineIndex => _lineIndex;
|
||||||
|
public int LineCount => _lines.Count;
|
||||||
public bool IsFirstLine => _lineIndex == 0;
|
public bool IsFirstLine => _lineIndex == 0;
|
||||||
public bool IsLastLine => _lineIndex == _lines.Count - 1;
|
public bool IsLastLine => _lineIndex == _lines.Count - 1;
|
||||||
public ILineEditorPrompt Prompt => _prompt;
|
|
||||||
public LineBuffer Buffer => _lines[_lineIndex];
|
public LineBuffer Buffer => _lines[_lineIndex];
|
||||||
|
|
||||||
public string Text => string.Join(Environment.NewLine, _lines.Select(x => x.Content));
|
public string Text => string.Join(Environment.NewLine, _lines.Select(x => x.Content));
|
||||||
|
|
||||||
public LineEditorState(ILineEditorPrompt prompt, string text)
|
public LineEditorState(ILineEditorPrompt prompt, string text)
|
||||||
{
|
{
|
||||||
_lines = new List<LineBuffer>(new[] { new LineBuffer(text) });
|
_lines = new List<LineBuffer>();
|
||||||
_prompt = prompt ?? throw new ArgumentNullException(nameof(prompt));
|
|
||||||
_lineIndex = 0;
|
_lineIndex = 0;
|
||||||
|
|
||||||
|
Prompt = prompt ?? throw new ArgumentNullException(nameof(prompt));
|
||||||
|
|
||||||
|
// Add all lines
|
||||||
|
foreach (var line in text.NormalizeNewLines().Split(new[] { '\n' }))
|
||||||
|
{
|
||||||
|
_lines.Add(new LineBuffer(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No lines?
|
||||||
|
if (_lines.Count == 0)
|
||||||
|
{
|
||||||
|
_lines.Add(new LineBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineBuffer GetBufferAt(int line)
|
||||||
|
{
|
||||||
|
return _lines[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Move(int line)
|
||||||
|
{
|
||||||
|
_lineIndex = Math.Max(0, Math.Min(line, LineCount - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveUp()
|
public bool MoveUp()
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Spectre.Console;
|
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
|
|
||||||
namespace RadLine
|
|
||||||
{
|
|
||||||
internal abstract class LineRenderingStrategy
|
|
||||||
{
|
|
||||||
private readonly IHighlighterAccessor _accessor;
|
|
||||||
|
|
||||||
protected LineRenderingStrategy(IHighlighterAccessor accessor)
|
|
||||||
{
|
|
||||||
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void Render(LineEditorState state, int? cursorPosition);
|
|
||||||
|
|
||||||
protected (string Content, int? Cursor) BuildLine(LineBuffer buffer, int width, int position)
|
|
||||||
{
|
|
||||||
var middleOfList = width / 2;
|
|
||||||
|
|
||||||
var skip = 0;
|
|
||||||
var take = buffer.Content.Length;
|
|
||||||
var pointer = position;
|
|
||||||
|
|
||||||
var scrollable = buffer.Content.Length > width;
|
|
||||||
if (scrollable)
|
|
||||||
{
|
|
||||||
skip = Math.Max(0, position - middleOfList);
|
|
||||||
take = Math.Min(width, buffer.Content.Length - skip);
|
|
||||||
|
|
||||||
if (buffer.Content.Length - position < middleOfList)
|
|
||||||
{
|
|
||||||
// Pointer should be below the end of the list
|
|
||||||
var diff = middleOfList - (buffer.Content.Length - position);
|
|
||||||
skip -= diff;
|
|
||||||
take += diff;
|
|
||||||
pointer = middleOfList + diff;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Take skip into account
|
|
||||||
pointer -= skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
string.Concat(buffer.Content.Skip(skip).Take(take)),
|
|
||||||
pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IRenderable Highlight(string text)
|
|
||||||
{
|
|
||||||
var highlighter = _accessor.Highlighter;
|
|
||||||
if (highlighter == null)
|
|
||||||
{
|
|
||||||
return new Text(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
var paragraph = new Paragraph();
|
|
||||||
foreach (var token in StringTokenizer.Tokenize(text))
|
|
||||||
{
|
|
||||||
var style = string.IsNullOrWhiteSpace(token) ? null : highlighter.Highlight(token);
|
|
||||||
paragraph.Append(token, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
return paragraph;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using Spectre.Console;
|
|
||||||
using Spectre.Console.Advanced;
|
|
||||||
|
|
||||||
namespace RadLine
|
|
||||||
{
|
|
||||||
internal sealed class AnsiRenderingStrategy : LineRenderingStrategy
|
|
||||||
{
|
|
||||||
private readonly IAnsiConsole _console;
|
|
||||||
|
|
||||||
public AnsiRenderingStrategy(IAnsiConsole console, IHighlighterAccessor accessor)
|
|
||||||
: base(accessor)
|
|
||||||
{
|
|
||||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Render(LineEditorState state, int? cursorPosition)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
|
|
||||||
var (prompt, margin) = state.Prompt.GetPrompt(state.LineIndex);
|
|
||||||
|
|
||||||
// Prepare
|
|
||||||
builder.Append("\u001b[?7l"); // Autowrap off
|
|
||||||
builder.Append("\u001b[2K"); // Clear the current line
|
|
||||||
builder.Append("\u001b[1G"); // Set cursor to beginning of line
|
|
||||||
|
|
||||||
// Render the prompt
|
|
||||||
builder.Append(_console.ToAnsi(prompt));
|
|
||||||
builder.Append(new string(' ', margin));
|
|
||||||
|
|
||||||
// Build the buffer
|
|
||||||
var width = _console.Profile.Width - prompt.Length - margin - 1;
|
|
||||||
var (content, position) = BuildLine(state.Buffer, width, cursorPosition ?? state.Buffer.CursorPosition);
|
|
||||||
|
|
||||||
// Output the buffer
|
|
||||||
builder.Append(_console.ToAnsi(Highlight(content)));
|
|
||||||
|
|
||||||
// Move the cursor to the right position
|
|
||||||
var cursorPos = position + prompt.Length + margin + 1;
|
|
||||||
builder.Append("\u001b[").Append(cursorPos).Append('G');
|
|
||||||
|
|
||||||
// Flush
|
|
||||||
_console.WriteAnsi(builder.ToString());
|
|
||||||
|
|
||||||
// Turn on auto wrap
|
|
||||||
builder.Append("\u001b[?7h");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
using System;
|
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace RadLine
|
|
||||||
{
|
|
||||||
internal sealed class FallbackRenderingStrategy : LineRenderingStrategy
|
|
||||||
{
|
|
||||||
private readonly IAnsiConsole _console;
|
|
||||||
|
|
||||||
public FallbackRenderingStrategy(IAnsiConsole console, IHighlighterAccessor accessor)
|
|
||||||
: base(accessor)
|
|
||||||
{
|
|
||||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Render(LineEditorState state, int? cursorPosition)
|
|
||||||
{
|
|
||||||
var (prompt, margin) = state.Prompt.GetPrompt(state.LineIndex);
|
|
||||||
|
|
||||||
// Hide the cursor
|
|
||||||
_console.Cursor.Hide();
|
|
||||||
|
|
||||||
// Clear the current line
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
Console.Write(new string(' ', _console.Profile.Width));
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
|
|
||||||
// Render the prompt
|
|
||||||
_console.Write(prompt);
|
|
||||||
_console.Write(new string(' ', margin));
|
|
||||||
|
|
||||||
// Build the buffer
|
|
||||||
var width = _console.Profile.Width - prompt.Length - margin - 1;
|
|
||||||
var (content, position) = BuildLine(state.Buffer, width, cursorPosition ?? state.Buffer.CursorPosition);
|
|
||||||
|
|
||||||
// Write the buffer
|
|
||||||
_console.Write(Highlight(content));
|
|
||||||
|
|
||||||
// Move the cursor to the right position
|
|
||||||
Console.CursorLeft = (position ?? 0) + prompt.Length + margin;
|
|
||||||
|
|
||||||
// Show the cursor
|
|
||||||
_console.Cursor.Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,10 +10,9 @@ namespace RadLine
|
||||||
private readonly IInputSource _source;
|
private readonly IInputSource _source;
|
||||||
private readonly IServiceProvider? _provider;
|
private readonly IServiceProvider? _provider;
|
||||||
private readonly IAnsiConsole _console;
|
private readonly IAnsiConsole _console;
|
||||||
private readonly LineBufferRenderer _presenter;
|
private readonly LineBufferRenderer _renderer;
|
||||||
|
|
||||||
public KeyBindings KeyBindings { get; }
|
public KeyBindings KeyBindings { get; }
|
||||||
|
|
||||||
public bool MultiLine { get; init; } = false;
|
public bool MultiLine { get; init; } = false;
|
||||||
public string Text { get; init; } = string.Empty;
|
public string Text { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
@ -26,11 +25,12 @@ namespace RadLine
|
||||||
_console = terminal ?? AnsiConsole.Console;
|
_console = terminal ?? AnsiConsole.Console;
|
||||||
_source = source ?? new DefaultInputSource(_console);
|
_source = source ?? new DefaultInputSource(_console);
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_presenter = new LineBufferRenderer(_console, this);
|
_renderer = new LineBufferRenderer(_console, this);
|
||||||
|
|
||||||
KeyBindings = new KeyBindings();
|
KeyBindings = new KeyBindings();
|
||||||
KeyBindings.Add(ConsoleKey.Tab, () => new AutoCompleteCommand(AutoComplete.Next));
|
KeyBindings.Add(ConsoleKey.Tab, () => new AutoCompleteCommand(AutoComplete.Next));
|
||||||
KeyBindings.Add(ConsoleKey.Tab, ConsoleModifiers.Control, () => new AutoCompleteCommand(AutoComplete.Previous));
|
KeyBindings.Add(ConsoleKey.Tab, ConsoleModifiers.Control, () => new AutoCompleteCommand(AutoComplete.Previous));
|
||||||
|
|
||||||
KeyBindings.Add<BackspaceCommand>(ConsoleKey.Backspace);
|
KeyBindings.Add<BackspaceCommand>(ConsoleKey.Backspace);
|
||||||
KeyBindings.Add<DeleteCommand>(ConsoleKey.Delete);
|
KeyBindings.Add<DeleteCommand>(ConsoleKey.Delete);
|
||||||
KeyBindings.Add<MoveHomeCommand>(ConsoleKey.Home);
|
KeyBindings.Add<MoveHomeCommand>(ConsoleKey.Home);
|
||||||
|
@ -47,11 +47,26 @@ namespace RadLine
|
||||||
KeyBindings.Add<NewLineCommand>(ConsoleKey.Enter, ConsoleModifiers.Shift);
|
KeyBindings.Add<NewLineCommand>(ConsoleKey.Enter, ConsoleModifiers.Shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsSupported(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
if (console is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(console));
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
console.Profile.Out.IsTerminal &&
|
||||||
|
console.Profile.Capabilities.Ansi &&
|
||||||
|
console.Profile.Capabilities.Interactive;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string?> ReadLine(CancellationToken cancellationToken)
|
public async Task<string?> ReadLine(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var cancelled = false;
|
var cancelled = false;
|
||||||
var state = new LineEditorState(Prompt, Text);
|
var state = new LineEditorState(Prompt, Text);
|
||||||
|
|
||||||
|
_renderer.Refresh(state);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await ReadLine(state, cancellationToken).ConfigureAwait(false);
|
var result = await ReadLine(state, cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -87,7 +102,7 @@ namespace RadLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_presenter.RenderLine(state, cursorPosition: 0);
|
_renderer.RenderLine(state, cursorPosition: 0);
|
||||||
|
|
||||||
// Move the cursor to the last line
|
// Move the cursor to the last line
|
||||||
while (state.MoveDown())
|
while (state.MoveDown())
|
||||||
|
@ -111,8 +126,6 @@ namespace RadLine
|
||||||
provider.RegisterOptional<ITextCompletion, ITextCompletion>(Completion);
|
provider.RegisterOptional<ITextCompletion, ITextCompletion>(Completion);
|
||||||
var context = new LineEditorContext(state.Buffer, provider);
|
var context = new LineEditorContext(state.Buffer, provider);
|
||||||
|
|
||||||
_presenter.RenderLine(state);
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
@ -148,7 +161,7 @@ namespace RadLine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the line
|
// Render the line
|
||||||
_presenter.RenderLine(state);
|
_renderer.RenderLine(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,20 +169,33 @@ namespace RadLine
|
||||||
{
|
{
|
||||||
using (_console.HideCursor())
|
using (_console.HideCursor())
|
||||||
{
|
{
|
||||||
_presenter.RenderLine(state, cursorPosition: 0);
|
|
||||||
state.AddLine();
|
state.AddLine();
|
||||||
|
|
||||||
// Moving the cursor won't work here if we're at
|
if (state.LineCount > _console.Profile.Height)
|
||||||
// the bottom of the screen, so let's insert a new line.
|
{
|
||||||
_console.WriteLine();
|
_console.Cursor.MoveDown();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.LineCount > _console.Profile.Height)
|
||||||
|
{
|
||||||
|
_renderer.Refresh(state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_renderer.RenderLine(state, cursorPosition: 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveUp(LineEditorState state)
|
private void MoveUp(LineEditorState state)
|
||||||
{
|
{
|
||||||
Move(state, state =>
|
Move(state, (state, moveCursor) =>
|
||||||
{
|
{
|
||||||
if (state.MoveUp())
|
if (state.MoveUp() && moveCursor)
|
||||||
{
|
{
|
||||||
_console.Cursor.MoveUp();
|
_console.Cursor.MoveUp();
|
||||||
}
|
}
|
||||||
|
@ -178,9 +204,9 @@ namespace RadLine
|
||||||
|
|
||||||
private void MoveDown(LineEditorState state)
|
private void MoveDown(LineEditorState state)
|
||||||
{
|
{
|
||||||
Move(state, state =>
|
Move(state, (state, moveCursor) =>
|
||||||
{
|
{
|
||||||
if (state.MoveDown())
|
if (state.MoveDown() && moveCursor)
|
||||||
{
|
{
|
||||||
_console.Cursor.MoveDown();
|
_console.Cursor.MoveDown();
|
||||||
}
|
}
|
||||||
|
@ -189,34 +215,62 @@ namespace RadLine
|
||||||
|
|
||||||
private void MoveFirst(LineEditorState state)
|
private void MoveFirst(LineEditorState state)
|
||||||
{
|
{
|
||||||
Move(state, state =>
|
Move(state, (state, moveCursor) =>
|
||||||
{
|
{
|
||||||
while (state.MoveUp())
|
while (state.MoveUp())
|
||||||
{
|
{
|
||||||
_console.Cursor.MoveUp();
|
if (moveCursor)
|
||||||
|
{
|
||||||
|
_console.Cursor.MoveUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveLast(LineEditorState state)
|
private void MoveLast(LineEditorState state)
|
||||||
{
|
{
|
||||||
Move(state, state =>
|
Move(state, (state, moveCursor) =>
|
||||||
{
|
{
|
||||||
while (state.MoveDown())
|
while (state.MoveDown())
|
||||||
{
|
{
|
||||||
_console.Cursor.MoveDown();
|
if (moveCursor)
|
||||||
|
{
|
||||||
|
_console.Cursor.MoveDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Move(LineEditorState state, Action<LineEditorState> action)
|
private void Move(LineEditorState state, Action<LineEditorState, bool> action)
|
||||||
{
|
{
|
||||||
using (_console.HideCursor())
|
using (_console.HideCursor())
|
||||||
{
|
{
|
||||||
_presenter.RenderLine(state, cursorPosition: 0);
|
if (state.LineCount > _console.Profile.Height)
|
||||||
var position = state.Buffer.Position;
|
{
|
||||||
action(state);
|
// Get the current position
|
||||||
state.Buffer.Move(position);
|
var position = state.Buffer.Position;
|
||||||
|
|
||||||
|
// Refresh everything
|
||||||
|
action(state, true);
|
||||||
|
_renderer.Refresh(state);
|
||||||
|
|
||||||
|
// Re-render the current line at the correct position
|
||||||
|
state.Buffer.Move(position);
|
||||||
|
_renderer.RenderLine(state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get the current position
|
||||||
|
var position = state.Buffer.Position;
|
||||||
|
|
||||||
|
// Reset the line
|
||||||
|
_renderer.RenderLine(state, cursorPosition: 0);
|
||||||
|
action(state, true);
|
||||||
|
|
||||||
|
// Render the current line at the correct position
|
||||||
|
state.Buffer.Move(position);
|
||||||
|
_renderer.RenderLine(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace RadLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Markup Markup, int Margin) GetPrompt(int line)
|
public (Markup Markup, int Margin) GetPrompt(ILineEditorState state, int line)
|
||||||
{
|
{
|
||||||
if (line == 0)
|
if (line == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,9 +11,9 @@ namespace RadLine
|
||||||
_style = style ?? new Style(foreground: Color.Yellow, background: Color.Blue);
|
_style = style ?? new Style(foreground: Color.Yellow, background: Color.Blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Markup Markup, int Margin) GetPrompt(int line)
|
public (Markup Markup, int Margin) GetPrompt(ILineEditorState state, int line)
|
||||||
{
|
{
|
||||||
return (new Markup(line.ToString("D2"), _style), 1);
|
return (new Markup((line + 1).ToString("D2"), _style), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Spectre.Console" Version="0.39.1-preview.0.26" />
|
<PackageReference Include="Spectre.Console" Version="0.39.1-preview.0.13" />
|
||||||
<PackageReference Include="IsExternalInit" Version="1.0.0" PrivateAssets="all" />
|
<PackageReference Include="IsExternalInit" Version="1.0.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Nullable" Version="1.3.0">
|
<PackageReference Include="Nullable" Version="1.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче