-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Use JLine 3 in the REPL #4680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Use JLine 3 in the REPL #4680
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
97acdfd
Use JLine 3 in the REPL
allanrenucci a437b74
Cleanup SourceFile API
allanrenucci 801c761
Take cursor position into account when inserting a new line
allanrenucci 94b2d81
Use JGit in the build instead of shell and bat scripts
allanrenucci 8fd65ac
Add dependency on jline-terminal-jna for Windows
allanrenucci d9c0c54
Polishing
allanrenucci f247799
Fix tests: add jline to test classpath
allanrenucci de2a214
In REPL, ENTER means SUBMIT when:
allanrenucci a0c3203
Fail early if not able to create a terminal
allanrenucci 7ff9103
Simplify REPL tests
allanrenucci 995ae34
Simplify compiler run creation in REPL
allanrenucci 3fd7a06
REPL state now store a Context instead of a Run
allanrenucci 5af8f55
More cleanup
allanrenucci b055bc4
Workaround #4709
allanrenucci e8fb849
Fix dotr script: add JLine jars to the classpath
allanrenucci 329d2a4
Tweak JLine configurations
allanrenucci 77ea7a7
Polishing
allanrenucci 926d426
Address review comments
allanrenucci File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package dotty.tools.repl | ||
|
||
import dotty.tools.dotc.core.Contexts.Context | ||
import dotty.tools.dotc.parsing.Scanners.Scanner | ||
import dotty.tools.dotc.parsing.Tokens._ | ||
import dotty.tools.dotc.printing.SyntaxHighlighting | ||
import dotty.tools.dotc.reporting.Reporter | ||
import dotty.tools.dotc.util.SourceFile | ||
import org.jline.reader | ||
import org.jline.reader.Parser.ParseContext | ||
import org.jline.reader._ | ||
import org.jline.reader.impl.history.DefaultHistory | ||
import org.jline.terminal.TerminalBuilder | ||
import org.jline.utils.AttributedString | ||
|
||
final class JLineTerminal extends java.io.Closeable { | ||
// import java.util.logging.{Logger, Level} | ||
// Logger.getLogger("org.jline").setLevel(Level.FINEST) | ||
|
||
private val terminal = TerminalBuilder.builder() | ||
.dumb(false) // fail early if not able to create a terminal | ||
.build() | ||
private val history = new DefaultHistory | ||
|
||
private def blue(str: String) = Console.BLUE + str + Console.RESET | ||
private val prompt = blue("scala> ") | ||
private val newLinePrompt = blue(" | ") | ||
|
||
/** Blockingly read line from `System.in` | ||
* | ||
* This entry point into JLine handles everything to do with terminal | ||
* emulation. This includes: | ||
* | ||
* - Multi-line support | ||
* - Copy-pasting | ||
* - History | ||
* - Syntax highlighting | ||
* - Auto-completions | ||
* | ||
* @throws EndOfFileException This exception is thrown when the user types Ctrl-D. | ||
*/ | ||
def readLine( | ||
completer: Completer // provide auto-completions | ||
)(implicit ctx: Context): String = { | ||
import LineReader.Option._ | ||
import LineReader._ | ||
val lineReader = LineReaderBuilder.builder() | ||
.terminal(terminal) | ||
.history(history) | ||
.completer(completer) | ||
.highlighter(new Highlighter) | ||
.parser(new Parser) | ||
.variable(SECONDARY_PROMPT_PATTERN, "%M") // A short word explaining what is "missing". | ||
// This is supplied from the EOFError.getMissing() method | ||
.variable(LIST_MAX, 400) // ask user when number of completions exceed this limit (default is 100) | ||
.option(INSERT_TAB, true) // at the beginning of the line, insert tab instead of completing | ||
.option(AUTO_FRESH_LINE, true) // if not at start of line before prompt, move to new line | ||
.build() | ||
|
||
lineReader.readLine(prompt) | ||
} | ||
|
||
def close() = terminal.close() | ||
|
||
/** Provide syntax highlighting */ | ||
private class Highlighter extends reader.Highlighter { | ||
def highlight(reader: LineReader, buffer: String): AttributedString = { | ||
val highlighted = SyntaxHighlighting(buffer).mkString | ||
AttributedString.fromAnsi(highlighted) | ||
} | ||
} | ||
|
||
/** Provide multi-line editing support */ | ||
private class Parser(implicit ctx: Context) extends reader.Parser { | ||
|
||
/** | ||
* @param cursor The cursor position within the line | ||
* @param line The unparsed line | ||
* @param word The current word being completed | ||
* @param wordCursor The cursor position within the current word | ||
*/ | ||
private class ParsedLine( | ||
val cursor: Int, val line: String, val word: String, val wordCursor: Int | ||
) extends reader.ParsedLine { | ||
// Using dummy values, not sure what they are used for | ||
def wordIndex = -1 | ||
def words = java.util.Collections.emptyList[String] | ||
} | ||
|
||
def parse(line: String, cursor: Int, context: ParseContext): reader.ParsedLine = { | ||
def parsedLine(word: String, wordCursor: Int) = | ||
new ParsedLine(cursor, line, word, wordCursor) | ||
// Used when no word is being completed | ||
def defaultParsedLine = parsedLine("", 0) | ||
|
||
def incomplete(): Nothing = throw new EOFError( | ||
// Using dummy values, not sure what they are used for | ||
/* line = */ -1, | ||
/* column = */ -1, | ||
/* message = */ "", | ||
/* missing = */ newLinePrompt) | ||
|
||
case class TokenData(token: Token, start: Int, end: Int) | ||
def currentToken: TokenData /* | Null */ = { | ||
val source = new SourceFile("<completions>", line) | ||
val scanner = new Scanner(source)(ctx.fresh.setReporter(Reporter.NoReporter)) | ||
while (scanner.token != EOF) { | ||
val start = scanner.offset | ||
val token = scanner.token | ||
scanner.nextToken() | ||
val end = scanner.lastOffset | ||
|
||
val isCurrentToken = cursor >= start && cursor <= end | ||
if (isCurrentToken) | ||
return TokenData(token, start, end) | ||
} | ||
null | ||
} | ||
|
||
context match { | ||
case ParseContext.ACCEPT_LINE => | ||
// ENTER means SUBMIT when | ||
// - cursor is at end (discarding whitespaces) | ||
// - and, input line is complete | ||
val cursorIsAtEnd = line.indexWhere(!_.isWhitespace, from = cursor) >= 0 | ||
if (cursorIsAtEnd || ParseResult.isIncomplete(line)) incomplete() | ||
else defaultParsedLine | ||
// using dummy values, resulting parsed line is probably unused | ||
|
||
case ParseContext.COMPLETE => | ||
// Parse to find completions (typically after a Tab). | ||
def isCompletable(token: Token) = isIdentifier(token) || isKeyword(token) | ||
currentToken match { | ||
case TokenData(token, start, end) if isCompletable(token) => | ||
val word = line.substring(start, end) | ||
val wordCursor = cursor - start | ||
parsedLine(word, wordCursor) | ||
case _ => | ||
defaultParsedLine | ||
} | ||
|
||
case _ => | ||
incomplete() | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we take an InputStream instead of hardcoding System.in ? For tests we shouldn't have to use System.in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JlineLine doesn't seem to let you configure the input stream and we should probably not modify
System.in
. We don't do end to end testing for the REPL, the tests don't rely on JLine