Skip to content

Commit

Permalink
pure ide: improvements (#3226)
Browse files Browse the repository at this point in the history
* repl: improve doc command

* pure ide: improvements
  • Loading branch information
akphi authored Nov 4, 2024
1 parent da7664e commit 074767b
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,63 +89,63 @@ public Client(MutableList<ReplExtension> replExtensions, MutableList<CompleterEx

public Client(MutableList<ReplExtension> replExtensions, MutableList<CompleterExtension> completerExtensions, PlanExecutor planExecutor, Path homeDirectory) throws Exception
{
this.replExtensions = replExtensions;
this.completerExtensions = completerExtensions;
this.planExecutor = planExecutor;
this.state = new ModelState(this.legendInterface, this.replExtensions);
this.terminal = TerminalBuilder.terminal();
this.homeDirectory = homeDirectory;
this.documentation = DocumentationGeneration.buildDocumentation();

this.initialize();
replExtensions.forEach(e -> e.initialize(this));

this.printDebug("[DEV] Legend REPL v" + DeploymentStateAndVersions.sdlc.buildVersion + " (" + DeploymentStateAndVersions.sdlc.commitIdAbbreviated + ")");
if (System.getProperty("legend.repl.initializationMessage") != null)
{
this.printDebug(StringEscapeUtils.unescapeJava(System.getProperty("legend.repl.initializationMessage")));
}
this.printDebug("Press 'Enter' or type 'help' to see the list of available commands.");
this.println("\n" + Logos.logos.get((int) (Logos.logos.size() * Math.random())) + "\n");

// NOTE: the order here matters, the default command 'help' should always go first
// and "catch-all" command 'execute' should always go last
this.commands = replExtensions
.flatCollect(ReplExtension::getExtraCommands)
.withAll(
Lists.mutable.with(
new Ext(this),
new Debug(this),
new Doc(this),
new Graph(this),
new Execute(this)
)
);
this.commands.add(0, new Help(this, this.commands));
this.reader = LineReaderBuilder.builder()
.terminal(terminal)
// Configure history file
// See https://github.com/jline/jline3/wiki/History
.variable(LineReader.HISTORY_FILE, this.getHomeDir().resolve("history"))
.variable(LineReader.HISTORY_FILE_SIZE, 1_000)
.variable(LineReader.HISTORY_IGNORE, ": *") // make sure empty space(s) are not persisted
// Disable cursor jumping to opening brace when typing closing brace
// See https://github.com/jline/jline3/issues/216
.variable(BLINK_MATCHING_PAREN, false)
// Make sure hitting <tab> at the beginning of line will insert a tab instead of triggering a completion
// which will cause error since the completer doesn't handle such case
// See https://github.com/jline/jline3/wiki/Completion
.option(LineReader.Option.INSERT_TAB, true)
// Make sure word navigation works properly with Alt + (left/right) arrow key
.variable(LineReader.WORDCHARS, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-$")
// Make sure to not break the completer when exclamation sign is present
// Do this by disabling history expansion
// See https://github.com/jline/jline3/issues/246
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
.highlighter(new JLine3Highlighter())
.parser(new JLine3Parser())
.completer(new JLine3Completer(this.commands))
.build();
this.replExtensions = replExtensions;
this.completerExtensions = completerExtensions;
this.planExecutor = planExecutor;
this.state = new ModelState(this.legendInterface, this.replExtensions);
this.terminal = TerminalBuilder.terminal();
this.homeDirectory = homeDirectory;
this.documentation = DocumentationGeneration.buildDocumentation();

this.initialize();
replExtensions.forEach(e -> e.initialize(this));

this.printDebug("[DEV] Legend REPL v" + DeploymentStateAndVersions.sdlc.buildVersion + " (" + DeploymentStateAndVersions.sdlc.commitIdAbbreviated + ")");
if (System.getProperty("legend.repl.initializationMessage") != null)
{
this.printDebug(StringEscapeUtils.unescapeJava(System.getProperty("legend.repl.initializationMessage")));
}
this.printDebug("Press 'Enter' or type 'help' to see the list of available commands.");
this.println("\n" + Logos.logos.get((int) (Logos.logos.size() * Math.random())) + "\n");

// NOTE: the order here matters, the default command 'help' should always go first
// and "catch-all" command 'execute' should always go last
this.commands = replExtensions
.flatCollect(ReplExtension::getExtraCommands)
.withAll(
Lists.mutable.with(
new Ext(this),
new Debug(this),
new Doc(this),
new Graph(this),
new Execute(this)
)
);
this.commands.add(0, new Help(this, this.commands));
this.reader = LineReaderBuilder.builder()
.terminal(terminal)
// Configure history file
// See https://github.com/jline/jline3/wiki/History
.variable(LineReader.HISTORY_FILE, this.getHomeDir().resolve("history"))
.variable(LineReader.HISTORY_FILE_SIZE, 1_000)
.variable(LineReader.HISTORY_IGNORE, ": *") // make sure empty space(s) are not persisted
// Disable cursor jumping to opening brace when typing closing brace
// See https://github.com/jline/jline3/issues/216
.variable(BLINK_MATCHING_PAREN, false)
// Make sure hitting <tab> at the beginning of line will insert a tab instead of triggering a completion
// which will cause error since the completer doesn't handle such case
// See https://github.com/jline/jline3/wiki/Completion
.option(LineReader.Option.INSERT_TAB, true)
// Make sure word navigation works properly with Alt + (left/right) arrow key
.variable(LineReader.WORDCHARS, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-$")
// Make sure to not break the completer when exclamation sign is present
// Do this by disabling history expansion
// See https://github.com/jline/jline3/issues/246
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
.highlighter(new JLine3Highlighter())
.parser(new JLine3Parser())
.completer(new JLine3Completer(this.commands))
.build();

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,66 @@

package org.finos.legend.engine.repl.core.commands;

import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.factory.Sets;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.utility.ListIterate;
import org.finos.legend.engine.repl.client.Client;
import org.finos.legend.engine.repl.client.jline3.JLine3Parser;
import org.finos.legend.engine.repl.core.Command;
import org.finos.legend.engine.repl.shared.DocumentationHelper;
import org.finos.legend.pure.m3.pct.aggregate.model.FunctionDocumentation;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;

import java.util.List;
import java.util.Map;

public class Doc implements Command
{
private final Client client;
private final DocGroupCompleter completer;
private final Map<String, FunctionDocumentation> functionDocIndex = Maps.mutable.empty();

public Doc(Client client)
{
this.client = client;
this.client.getDocumentedFunctions().forEach(key ->
{
FunctionDocumentation doc = this.client.getFunctionDocumentation(key);
String path = doc.functionDefinition.sourceId.substring(doc.reportScope.filePath.length(), doc.functionDefinition.sourceId.lastIndexOf(".pure"));
functionDocIndex.put(path, doc);
});
this.completer = new DocGroupCompleter(functionDocIndex);
}

@Override
public String documentation()
{
return "doc <function>";
return "doc (<function directory path>) | docFn <function path>";
}

@Override
public String description()
{
return "show documentation of the specified Pure function";
return "'doc' command supports browsing all supported Pure functions\n" +
"'docFn' command shows documentation of the specified Pure function";
}

@Override
public boolean process(String line) throws Exception
{
if (line.startsWith("doc"))
if (line.startsWith("docFn"))
{
String[] tokens = line.split(" ");
String path = tokens[1];
String path = tokens.length > 1 ? tokens[1] : "";
if (path.isEmpty())
{
client.printError("Function path is required");
return true;
}
FunctionDocumentation functionDocumentation = this.client.getFunctionDocumentation(path);
if (functionDocumentation != null)
{
Expand All @@ -63,16 +85,84 @@ public boolean process(String line) throws Exception
}
return true;
}
else if (line.startsWith("doc"))
{
String[] tokens = line.split(" ");
String path = tokens.length > 1 ? tokens[1] : "";
if (path.isEmpty())
{
client.printError("Function path is required");
return true;
}
FunctionDocumentation functionDocumentation = this.functionDocIndex.get(path);
if (functionDocumentation != null)
{
client.println(DocumentationHelper.generateANSIFunctionDocumentation(functionDocumentation, client.getDocumentationAdapterKeys()));
}
else
{
client.printError("No documentation found for function: " + path);
}
return true;
}
return false;
}

@Override
public MutableList<Candidate> complete(String inScope, LineReader lineReader, ParsedLine parsedLine)
{
if (inScope.startsWith("doc"))
if (inScope.startsWith("docFn"))
{
return ListIterate.collect(client.getDocumentedFunctions(), Candidate::new);
}
else if (inScope.startsWith("doc"))
{
MutableList<String> words = Lists.mutable.withAll(parsedLine.words()).drop(2);
String path = words.makeString("");
MutableList<Candidate> list = Lists.mutable.empty();
completer.complete(lineReader, new JLine3Parser.MyParsedLine(new JLine3Parser.ParserResult(parsedLine.line(), Lists.mutable.with("doc", " ", path))), list);
return list;
}
return null;
}

private static class DocGroupCompleter implements org.jline.reader.Completer
{
private final Map<String, FunctionDocumentation> functionDocIndex;

public DocGroupCompleter(Map<String, FunctionDocumentation> functionDocIndex)
{
this.functionDocIndex = functionDocIndex;
}

public void complete(LineReader reader, ParsedLine commandLine, final List<Candidate> candidates)
{
String buffer = commandLine.word().substring(0, commandLine.wordCursor());
String current;
String sep = "/";
int lastSep = buffer.lastIndexOf(sep);
try
{
current = lastSep >= 0 ? buffer.substring(0, lastSep + 1) : "";
Sets.mutable.withAll(functionDocIndex.keySet())
.select(path -> path.startsWith(current))
.collect(path ->
{
String childNode = path.substring(current.length());
return current + childNode.substring(0, !childNode.contains(sep) ? childNode.length() : childNode.indexOf(sep) + 1);
})
.toSortedList()
.forEach(value ->
{
candidates.add(value.endsWith(sep)
? new Candidate(value, sep + value, null, null, "/", null, false)
: new Candidate(value, value, null, null, null, null, true));
});
}
catch (Exception e)
{
// Ignore
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,11 @@ protected MutableList<Function0<Void>> getSteps()
"history with ArrowUp/Down key, etc.")));
this.println("");
this.println(wrap("The function documentation lookup tool can be quite helpful, for example, to view documentation and " +
"usages for 'filter', use the following command:"));
this.printCommand("doc meta::pure::functions::relation::filter");
"usages for function 'filter', use the following command:"));
this.printCommand("doc collection/iteration/filter");
this.println(ansiDim(printRule(null)));
this.println(DocumentationHelper.generateANSIFunctionDocumentation(this.client.getFunctionDocumentation("meta::pure::functions::relation::filter"), this.client.getDocumentationAdapterKeys()));
this.client.addCommandToHistory("doc meta::pure::functions::relation::filter");
this.client.addCommandToHistory("doc collection/iteration/filter");
this.println(ansiDim(printRule(null)));
this.println("");
this.println(wrap("Also, don't forget to hit the 'Tab' key while typing up an expression, the compiler can help validate " +
Expand Down
Loading

0 comments on commit 074767b

Please sign in to comment.