Skip to content

Fix #5183: (REPL) Only insert line break when cursor is not on the last line #5652

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 4 commits into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions compiler/src/dotty/tools/repl/JLineTerminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ final class JLineTerminal extends java.io.Closeable {
def words = java.util.Collections.emptyList[String]
}

def parse(line: String, cursor: Int, context: ParseContext): reader.ParsedLine = {
def parse(input: String, cursor: Int, context: ParseContext): reader.ParsedLine = {
def parsedLine(word: String, wordCursor: Int) =
new ParsedLine(cursor, line, word, wordCursor)
new ParsedLine(cursor, input, word, wordCursor)
// Used when no word is being completed
def defaultParsedLine = parsedLine("", 0)

Expand All @@ -110,7 +110,7 @@ final class JLineTerminal extends java.io.Closeable {

case class TokenData(token: Token, start: Int, end: Int)
def currentToken: TokenData /* | Null */ = {
val source = new SourceFile("<completions>", line)
val source = new SourceFile("<completions>", input)
val scanner = new Scanner(source)(ctx.fresh.setReporter(Reporter.NoReporter))
while (scanner.token != EOF) {
val start = scanner.offset
Expand All @@ -125,23 +125,22 @@ final class JLineTerminal extends java.io.Closeable {
null
}

def acceptLine = {
val onLastLine = !input.substring(cursor).contains(System.lineSeparator)
onLastLine && !ParseResult.isIncomplete(input)
}

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))
defaultParsedLine // using dummy values, resulting parsed line is probably unused
else
incomplete()
case ParseContext.ACCEPT_LINE if acceptLine =>
// using dummy values, resulting parsed input is probably unused
defaultParsedLine

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 word = input.substring(start, end)
val wordCursor = cursor - start
parsedLine(word, wordCursor)
case _ =>
Expand Down
20 changes: 15 additions & 5 deletions compiler/src/dotty/tools/repl/ReplCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,36 @@ class ReplCompiler extends Compiler {

implicit val ctx: Context = state.context

// If trees is of the form `{ def1; def2; def3 }` then `List(def1, def2, def3)`
val flattened = trees match {
case List(Block(stats, expr)) =>
if (expr eq EmptyTree) stats // happens when expr is not an expression
else stats :+ expr
case _ =>
trees
}

var valIdx = state.valIndex
val defs = new mutable.ListBuffer[Tree]

val defs = trees.flatMap {
flattened.foreach {
case expr @ Assign(id: Ident, _) =>
// special case simple reassignment (e.g. x = 3)
// in order to print the new value in the REPL
val assignName = (id.name ++ str.REPL_ASSIGN_SUFFIX).toTermName
val assign = ValDef(assignName, TypeTree(), id).withPos(expr.pos)
List(expr, assign)
defs += expr += assign
case expr if expr.isTerm =>
val resName = (str.REPL_RES_PREFIX + valIdx).toTermName
valIdx += 1
val vd = ValDef(resName, TypeTree(), expr).withPos(expr.pos)
vd :: Nil
defs += vd
case other =>
other :: Nil
defs += other
}

Definitions(
defs,
defs.toList,
state.copy(
objectIndex = state.objectIndex + (if (defs.isEmpty) 0 else 1),
valIndex = valIdx
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/repl/ReplDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,16 @@ class ReplDriver(settings: Array[String],
denot.symbol.owner == defn.ObjectClass ||
denot.symbol.isConstructor
}
.sortBy(_.name)

val vals =
info.fields
.filterNot(_.symbol.is(ParamAccessor | Private | Synthetic | Module))
.filter(_.symbol.name.is(SimpleNameKind))
.sortBy(_.name)

val typeAliases =
info.bounds.hi.typeMembers.filter(_.symbol.info.isTypeAlias)
info.bounds.hi.typeMembers.filter(_.symbol.info.isTypeAlias).sortBy(_.name)

(
typeAliases.map("// defined alias " + _.symbol.showUser) ++
Expand Down
4 changes: 0 additions & 4 deletions compiler/test-resources/repl/errmsgs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ scala> val x: List[Int] = "foo" :: List(1)
| ^^^^^
| Found: String("foo")
| Required: Int
scala> { def f: Int = g; val x: Int = 1; def g: Int = 5; }
1 | { def f: Int = g; val x: Int = 1; def g: Int = 5; }
| ^
| g is a forward reference extending over the definition of x
scala> while ((( foo ))) {}
1 | while ((( foo ))) {}
| ^^^
Expand Down
22 changes: 22 additions & 0 deletions compiler/test-resources/repl/top-level-block
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
scala> { case class A(); def foo = 1; 2 }
// defined case class A
def foo: Int
val res0: Int = 2

scala> foo
val res1: Int = 1

scala> A()
val res2: A = A()

scala> { def f: Int = g; def g: Int = 5 }
def f: Int
def g: Int

scala> f + g
val res3: Int = 10

scala> { val x = 3; 4; val y = 5 }
val res4: Int = 4
val x: Int = 3
val y: Int = 5
15 changes: 8 additions & 7 deletions compiler/test/dotty/tools/repl/ReplCompilerTests.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package dotty.tools.repl

import java.lang.System.{lineSeparator => EOL}

import org.junit.Assert._
import org.junit.{Ignore, Test}

class ReplCompilerTests extends ReplTest {

private def lines() =
storedOutput().trim.linesIterator.toList

@Test def compileSingle = fromInitialState { implicit state =>
run("def foo: 1 = 1")
assertEquals("def foo: Int(1)", storedOutput().trim)
Expand Down Expand Up @@ -47,13 +48,13 @@ class ReplCompilerTests extends ReplTest {

val expected = List(
"def foo: Int",
"val x: Int = 10",
"val res0: Int = 2",
"var y: Int = 5",
"val res1: Int = 20"
"val res1: Int = 20",
"val x: Int = 10",
"var y: Int = 5"
)

assertEquals(expected, storedOutput().split(EOL).toList)
assertEquals(expected, lines())
}

@Test def testImportMutable =
Expand Down Expand Up @@ -124,6 +125,6 @@ class ReplCompilerTests extends ReplTest {
)

run(source)
assertEquals(expected, storedOutput().split(EOL).toList)
assertEquals(expected, lines())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class WorksheetTest {

@Test def patternMatching1: Unit = {
ws"""${m1}val (foo, bar) = (1, 2)""".withSource
.run(m1, s"1:val foo: Int = 1${nl}val bar: Int = 2")
.run(m1, s"1:val bar: Int = 2${nl}val foo: Int = 1")
}

@Test def evaluationException: Unit = {
Expand Down