Skip to content

Commit

Permalink
REPL: Basic autocompletion support
Browse files Browse the repository at this point in the history
This adds basic completion of keys in the REPL scope (including global
scope). There might be bugs here, but it seems to work fairly well in
basic cases.
  • Loading branch information
paulirwin committed Aug 20, 2023
1 parent 92a99fb commit ab11246
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 19 deletions.
4 changes: 4 additions & 0 deletions Colibri.Core/ColibriRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ public ColibriRuntime(RuntimeOptions? options = null)

_userScope = _globalScope.CreateChildScope();
}

public Scope GlobalScope => _globalScope;

public Scope UserScope => _userScope;

private void LoadStandardLibraries(bool import)
{
Expand Down
15 changes: 15 additions & 0 deletions Colibri.Core/Scope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,19 @@ public bool TryResolveLibrary(LibraryName name, [NotNullWhen(true)] out Library?
library = null;
return false;
}

public IEnumerable<string> FlattenAllKeys()
{
var scope = this;

while (scope != null)
{
foreach (var key in scope.Env.Keys)
{
yield return key;
}

scope = scope.Parent;
}
}
}
2 changes: 1 addition & 1 deletion Colibri/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private static async Task FileHandler(FileInfo? file)
{
if (file == null)
{
await Repl.RunRepl();
await new Repl(new ReplOptions()).RunRepl();
}
else
{
Expand Down
33 changes: 33 additions & 0 deletions Colibri/PromptConfig/PromptCompletions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Antlr4.Runtime;
using Colibri.Core;
using PrettyPrompt.Completion;

namespace Colibri.PromptConfig;

public static class PromptCompletions
{
public static IReadOnlyList<CompletionItem> GetCompletionItems(
ColibriRuntime runtime,
string text,
int caret)
{
// HACK.PI: this code is _highly_ inefficient
var keys = runtime.UserScope.FlattenAllKeys();
var lexer = new ColibriLexer(new AntlrInputStream(text));

var tokens = lexer.GetAllTokens();

if (tokens.Count > 0)
{
var lastToken = tokens[^1];
var lastTokenText = lastToken.Text;

if (lastToken.Type == ColibriLexer.IDENTIFIER)
{
keys = keys.Where(i => i.StartsWith(lastTokenText));
}
}

return keys.Select(i => new CompletionItem(i)).ToList();
}
}
17 changes: 17 additions & 0 deletions Colibri/PromptConfig/ReplPromptCallbacks.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
using Colibri.Core;
using PrettyPrompt;
using PrettyPrompt.Completion;
using PrettyPrompt.Consoles;
using PrettyPrompt.Documents;

namespace Colibri.PromptConfig;

public class ReplPromptCallbacks : PromptCallbacks
{
private readonly ColibriRuntime _runtime;

public ReplPromptCallbacks(ColibriRuntime runtime)
{
_runtime = runtime;
}

protected override IEnumerable<(KeyPressPattern Pattern, KeyPressCallbackAsync Callback)> GetKeyPressCallbacks()
{
yield return (new KeyPressPattern(ConsoleModifiers.Control, ConsoleKey.D),
Expand All @@ -13,4 +23,11 @@ public class ReplPromptCallbacks : PromptCallbacks
yield return (new KeyPressPattern(ConsoleModifiers.Control, ConsoleKey.C),
(_, _, _) => Task.FromResult<KeyPressCallbackResult?>(new ExitAppResult()));
}

protected override Task<IReadOnlyList<CompletionItem>> GetCompletionItemsAsync(string text, int caret, TextSpan spanToBeReplaced, CancellationToken cancellationToken)
{
var completionItems = PromptCompletions.GetCompletionItems(_runtime, text, caret);

return Task.FromResult<IReadOnlyList<CompletionItem>>(completionItems);
}
}
47 changes: 29 additions & 18 deletions Colibri/Repl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,34 @@

namespace Colibri;

public static class Repl
public class Repl
{
public static async Task RunRepl()
{
PrintIntro();
private readonly ReplOptions _options;
private ColibriRuntime _runtime;
private IPrompt _prompt;
private readonly PromptConfiguration _promptConfig;

var options = new ReplOptions();
public Repl(ReplOptions options)
{
_options = options;
_runtime = CreateRuntime(options);
_promptConfig = ReplPromptConfig.GetPromptConfig();
_prompt = CreatePrompt();
}

var runtime = CreateRuntime(options);
public async Task RunRepl()
{
PrintIntro();

var visitor = new ColibriVisitor();

var promptConfig = ReplPromptConfig.GetPromptConfig();

var prompt = new Prompt(
callbacks: new ReplPromptCallbacks(),
configuration: promptConfig,
persistentHistoryFilepath: ReplPromptConfig.GetPromptHistoryDirectory()
);

string? programText = null;

while (true)
{
promptConfig.Prompt = programText == null ? ">>> " : "... ";
_promptConfig.Prompt = programText == null ? ">>> " : "... ";

var promptResult = await prompt.ReadLineAsync();
var promptResult = await _prompt.ReadLineAsync();

if (promptResult is ExitAppResult)
{
Expand Down Expand Up @@ -71,7 +72,8 @@ public static async Task RunRepl()

if (input.Equals("reset", StringComparison.OrdinalIgnoreCase))
{
runtime = CreateRuntime(options);
_runtime = CreateRuntime(_options);
_prompt = CreatePrompt();
Console.WriteLine("Runtime environment reset to defaults.");
continue;
}
Expand Down Expand Up @@ -106,7 +108,7 @@ public static async Task RunRepl()

if (programNode != null)
{
EvaluateAndPrint(runtime, options, programNode);
EvaluateAndPrint(_runtime, _options, programNode);
programText = null;
}
}
Expand Down Expand Up @@ -175,4 +177,13 @@ private static ColibriRuntime CreateRuntime(ReplOptions options)

return runtime;
}

private Prompt CreatePrompt()
{
return new Prompt(
callbacks: new ReplPromptCallbacks(_runtime),
configuration: _promptConfig,
persistentHistoryFilepath: ReplPromptConfig.GetPromptHistoryDirectory()
);
}
}

0 comments on commit ab11246

Please sign in to comment.