Skip to content

Commit bdf3ef4

Browse files
authored
Merge pull request #4680 from dotty-staging/jline3
Use JLine 3 in the REPL
2 parents f093ba2 + 926d426 commit bdf3ef4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+488
-2764
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ object CompilationUnit {
3737

3838
/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
3939
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
40-
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)
40+
mkCompilationUnit(SourceFile(clsd.symbol.associatedFile, Array.empty), unpickled, forceTrees)
4141

4242
/** Make a compilation unit, given picked bytes and unpickled tree */
4343
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {

compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ class ReadTastyTreesFromClasses extends FrontEnd {
4343
case unpickler: tasty.DottyUnpickler =>
4444
if (cls.tree.isEmpty) None
4545
else {
46-
val source = SourceFile(cls.associatedFile, Array())
47-
val unit = mkCompilationUnit(source, cls.tree, forceTrees = true)
46+
val unit = mkCompilationUnit(cls, cls.tree, forceTrees = true)
4847
unit.pickled += (cls -> unpickler.unpickler.bytes)
4948
Some(unit)
5049
}

compiler/src/dotty/tools/dotc/interactive/Interactive.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ object Interactive {
219219
case _ => getScopeCompletions(ctx)
220220
}
221221

222-
val sortedCompletions = completions.toList.sortBy(_.name: Name)
223-
interactiv.println(i"completion with pos = $pos, prefix = $prefix, termOnly = $termOnly, typeOnly = $typeOnly = $sortedCompletions%, %")
224-
(completionPos, sortedCompletions)
222+
val completionList = completions.toList
223+
interactiv.println(i"completion with pos = $pos, prefix = $prefix, termOnly = $termOnly, typeOnly = $typeOnly = $completionList%, %")
224+
(completionPos, completionList)
225225
}
226226

227227
/** Possible completions of members of `prefix` which are accessible when called inside `boundary` */

compiler/src/dotty/tools/dotc/quoted/QuoteCompiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ class QuoteCompiler(directory: AbstractFile) extends Compiler {
6262
val tree =
6363
if (putInClass) inClass(exprUnit.expr)
6464
else PickledQuotes.quotedExprToTree(exprUnit.expr)
65-
val source = new SourceFile("", Seq())
65+
val source = new SourceFile("", "")
6666
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
6767
case typeUnit: TypeCompilationUnit =>
6868
assert(!putInClass)
6969
val tree = PickledQuotes.quotedTypeToTree(typeUnit.tpe)
70-
val source = new SourceFile("", Seq())
70+
val source = new SourceFile("", "")
7171
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
7272
}
7373
}

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ object Reporter {
2323
simple.report(m)
2424
}
2525
}
26+
27+
/** A reporter that ignores reports, and doesn't record errors */
28+
@sharable object NoReporter extends Reporter {
29+
def doReport(m: MessageContainer)(implicit ctx: Context) = ()
30+
override def report(m: MessageContainer)(implicit ctx: Context): Unit = ()
31+
}
2632
}
2733

2834
import Reporter._
@@ -157,8 +163,10 @@ abstract class Reporter extends interfaces.ReporterResult {
157163
finally incompleteHandler = saved
158164
}
159165

160-
var errorCount = 0
161-
var warningCount = 0
166+
private[this] var _errorCount = 0
167+
private[this] var _warningCount = 0
168+
def errorCount = _errorCount
169+
def warningCount = _warningCount
162170
def hasErrors = errorCount > 0
163171
def hasWarnings = warningCount > 0
164172
private[this] var errors: List[Error] = Nil
@@ -185,10 +193,10 @@ abstract class Reporter extends interfaces.ReporterResult {
185193
doReport(m)(ctx.addMode(Mode.Printing))
186194
m match {
187195
case m: ConditionalWarning if !m.enablingOption.value => unreportedWarnings(m.enablingOption.name) += 1
188-
case m: Warning => warningCount += 1
196+
case m: Warning => _warningCount += 1
189197
case m: Error =>
190198
errors = m :: errors
191-
errorCount += 1
199+
_errorCount += 1
192200
case m: Info => // nothing to do here
193201
// match error if d is something else
194202
}

compiler/src/dotty/tools/dotc/util/SourceFile.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ object ScriptSourceFile {
3737

3838
case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfaces.SourceFile {
3939

40-
def this(_file: AbstractFile, codec: Codec) = this(_file, new String(_file.toByteArray, codec.charSet).toCharArray)
41-
def this(sourceName: String, cs: Seq[Char]) = this(new VirtualFile(sourceName), cs.toArray)
42-
def this(file: AbstractFile, cs: Seq[Char]) = this(file, cs.toArray)
40+
def this(file: AbstractFile, codec: Codec) = this(file, new String(file.toByteArray, codec.charSet).toCharArray)
41+
def this(name: String, content: String) = this(new VirtualFile(name), content.toCharArray)
4342

4443
/** Tab increment; can be overridden */
4544
def tabInc = 8
@@ -150,7 +149,7 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfac
150149
override def toString = file.toString
151150
}
152151

153-
@sharable object NoSource extends SourceFile("<no source>", Nil) {
152+
@sharable object NoSource extends SourceFile("<no source>", "") {
154153
override def exists = false
155154
override def atPos(pos: Position): SourcePosition = NoSourcePosition
156155
}

compiler/src/dotty/tools/repl/AmmoniteReader.scala

Lines changed: 0 additions & 142 deletions
This file was deleted.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package dotty.tools.repl
2+
3+
import dotty.tools.dotc.core.Contexts.Context
4+
import dotty.tools.dotc.parsing.Scanners.Scanner
5+
import dotty.tools.dotc.parsing.Tokens._
6+
import dotty.tools.dotc.printing.SyntaxHighlighting
7+
import dotty.tools.dotc.reporting.Reporter
8+
import dotty.tools.dotc.util.SourceFile
9+
import org.jline.reader
10+
import org.jline.reader.Parser.ParseContext
11+
import org.jline.reader._
12+
import org.jline.reader.impl.history.DefaultHistory
13+
import org.jline.terminal.TerminalBuilder
14+
import org.jline.utils.AttributedString
15+
16+
final class JLineTerminal extends java.io.Closeable {
17+
// import java.util.logging.{Logger, Level}
18+
// Logger.getLogger("org.jline").setLevel(Level.FINEST)
19+
20+
private val terminal = TerminalBuilder.builder()
21+
.dumb(false) // fail early if not able to create a terminal
22+
.build()
23+
private val history = new DefaultHistory
24+
25+
private def blue(str: String) = Console.BLUE + str + Console.RESET
26+
private val prompt = blue("scala> ")
27+
private val newLinePrompt = blue(" | ")
28+
29+
/** Blockingly read line from `System.in`
30+
*
31+
* This entry point into JLine handles everything to do with terminal
32+
* emulation. This includes:
33+
*
34+
* - Multi-line support
35+
* - Copy-pasting
36+
* - History
37+
* - Syntax highlighting
38+
* - Auto-completions
39+
*
40+
* @throws EndOfFileException This exception is thrown when the user types Ctrl-D.
41+
*/
42+
def readLine(
43+
completer: Completer // provide auto-completions
44+
)(implicit ctx: Context): String = {
45+
import LineReader.Option._
46+
import LineReader._
47+
val lineReader = LineReaderBuilder.builder()
48+
.terminal(terminal)
49+
.history(history)
50+
.completer(completer)
51+
.highlighter(new Highlighter)
52+
.parser(new Parser)
53+
.variable(SECONDARY_PROMPT_PATTERN, "%M") // A short word explaining what is "missing".
54+
// This is supplied from the EOFError.getMissing() method
55+
.variable(LIST_MAX, 400) // ask user when number of completions exceed this limit (default is 100)
56+
.option(INSERT_TAB, true) // at the beginning of the line, insert tab instead of completing
57+
.option(AUTO_FRESH_LINE, true) // if not at start of line before prompt, move to new line
58+
.build()
59+
60+
lineReader.readLine(prompt)
61+
}
62+
63+
def close() = terminal.close()
64+
65+
/** Provide syntax highlighting */
66+
private class Highlighter extends reader.Highlighter {
67+
def highlight(reader: LineReader, buffer: String): AttributedString = {
68+
val highlighted = SyntaxHighlighting(buffer).mkString
69+
AttributedString.fromAnsi(highlighted)
70+
}
71+
}
72+
73+
/** Provide multi-line editing support */
74+
private class Parser(implicit ctx: Context) extends reader.Parser {
75+
76+
/**
77+
* @param cursor The cursor position within the line
78+
* @param line The unparsed line
79+
* @param word The current word being completed
80+
* @param wordCursor The cursor position within the current word
81+
*/
82+
private class ParsedLine(
83+
val cursor: Int, val line: String, val word: String, val wordCursor: Int
84+
) extends reader.ParsedLine {
85+
// Using dummy values, not sure what they are used for
86+
def wordIndex = -1
87+
def words = java.util.Collections.emptyList[String]
88+
}
89+
90+
def parse(line: String, cursor: Int, context: ParseContext): reader.ParsedLine = {
91+
def parsedLine(word: String, wordCursor: Int) =
92+
new ParsedLine(cursor, line, word, wordCursor)
93+
// Used when no word is being completed
94+
def defaultParsedLine = parsedLine("", 0)
95+
96+
def incomplete(): Nothing = throw new EOFError(
97+
// Using dummy values, not sure what they are used for
98+
/* line = */ -1,
99+
/* column = */ -1,
100+
/* message = */ "",
101+
/* missing = */ newLinePrompt)
102+
103+
case class TokenData(token: Token, start: Int, end: Int)
104+
def currentToken: TokenData /* | Null */ = {
105+
val source = new SourceFile("<completions>", line)
106+
val scanner = new Scanner(source)(ctx.fresh.setReporter(Reporter.NoReporter))
107+
while (scanner.token != EOF) {
108+
val start = scanner.offset
109+
val token = scanner.token
110+
scanner.nextToken()
111+
val end = scanner.lastOffset
112+
113+
val isCurrentToken = cursor >= start && cursor <= end
114+
if (isCurrentToken)
115+
return TokenData(token, start, end)
116+
}
117+
null
118+
}
119+
120+
context match {
121+
case ParseContext.ACCEPT_LINE =>
122+
// ENTER means SUBMIT when
123+
// - cursor is at end (discarding whitespaces)
124+
// - and, input line is complete
125+
val cursorIsAtEnd = line.indexWhere(!_.isWhitespace, from = cursor) >= 0
126+
if (cursorIsAtEnd || ParseResult.isIncomplete(line)) incomplete()
127+
else defaultParsedLine
128+
// using dummy values, resulting parsed line is probably unused
129+
130+
case ParseContext.COMPLETE =>
131+
// Parse to find completions (typically after a Tab).
132+
def isCompletable(token: Token) = isIdentifier(token) || isKeyword(token)
133+
currentToken match {
134+
case TokenData(token, start, end) if isCompletable(token) =>
135+
val word = line.substring(start, end)
136+
val wordCursor = cursor - start
137+
parsedLine(word, wordCursor)
138+
case _ =>
139+
defaultParsedLine
140+
}
141+
142+
case _ =>
143+
incomplete()
144+
}
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)