Skip to content
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

REPL: add syntax highlighting, like the Scala 3 REPL (and Ammonite) #12273

Open
SethTisue opened this issue Mar 5, 2020 · 10 comments
Open

REPL: add syntax highlighting, like the Scala 3 REPL (and Ammonite) #12273

SethTisue opened this issue Mar 5, 2020 · 10 comments
Assignees
Milestone

Comments

@SethTisue
Copy link
Member

SethTisue commented Mar 5, 2020

as @allanrenucci commented at scala/scala#7645 (comment) ,

Here is the implementation in Dotty for reference. I believe it would be straigforward to port our syntax highlighter

someone want to have a look and see it how even works? does it involve external dependencies?

JLine info page on this is https://github.com/jline/jline3/wiki/Highlighting-and-parsing

@soronpo

This comment was marked as outdated.

@SethTisue

This comment was marked as outdated.

@soronpo

This comment was marked as outdated.

@SethTisue SethTisue changed the title JLine 3: add syntax highlighting, like the Dotty REPL (and Ammonite) REPL: add syntax highlighting, like the Dotty REPL (and Ammonite) Dec 8, 2020
@SethTisue SethTisue transferred this issue from scala/scala-dev Dec 8, 2020
@SethTisue SethTisue added the repl label Dec 8, 2020
@SethTisue SethTisue self-assigned this Dec 8, 2020
@SethTisue SethTisue added this to the 2.13.5 milestone Dec 8, 2020
@SethTisue
Copy link
Member Author

SethTisue commented Dec 14, 2020

@retronym passed along a link to this which might be useful: scala/scala@60ee992

oh, but Paul ripped some of that out again in scala/scala@317a105

@retronym
Copy link
Member

retronym commented Dec 15, 2020

scala> val unit = newCompilationUnit("class      C { def foo = for (x <- (1 to 10)) yield null }"); val scanner = new syntaxAnalyzer.UnitScanner(unit); scanner.init(); while (scanner.token != ast.parser.Tokens.EOF) { val x = scanner.toString; val o = scanner.offset; scanner.
nextToken(); println((x, o, scanner.lastOffset))}
('class',0,5)
(id(C),11,12)
('{',13,14)
('def',15,18)
(id(foo),19,22)
('=',23,24)
('for',25,28)
('(',29,30)
(id(x),30,31)
('<-',32,34)
('(',35,36)
(int(1),36,37)
(id(to),38,40)
(int(10),41,43)
(')',43,44)
(')',44,45)
('yield',46,51)
('null',52,56)
('}',57,58)
val unit: $r.intp.global.CompilationUnit = <console>
val scanner: $r.intp.global.syntaxAnalyzer.UnitScanner = eof

@retronym
Copy link
Member

If you'd like to detect // comments, a small change to the the parser API would be needed.

Here's what can be done already:

val unit = newCompilationUnit("class      C { def foo = for (x <- (1 to 10)) yield null /* C1 */ // C2}"); val scanner = new global.syntaxAnalyzer.UnitScanner(unit, Nil) { override def processCommentChar(): Unit = { println((s"COMMENT($ch)", charOffset)) } }; scanner.
init(); while (scanner.token != ast.parser.Tokens.EOF) { val x = scanner.toString; val o = scanner.offset; scanner.nextToken(); println((x, o, scanner.lastOffset))}
('class',0,5)
(id(C),11,12)
('{',13,14)
('def',15,18)
(id(foo),19,22)
('=',23,24)
('for',25,28)
('(',29,30)
(id(x),30,31)
('<-',32,34)
('(',35,36)
(int(1),36,37)
(id(to),38,40)
(int(10),41,43)
(')',43,44)
(')',44,45)
('yield',46,51)
(COMMENT( ),60)
(COMMENT(C),61)
(COMMENT(1),62)
(COMMENT( ),63)
(COMMENT(*),64)
(COMMENT(/),65)
('null',52,56)
val unit: $r.intp.global.CompilationUnit = <console>
val scanner: $r.global.syntaxAnalyzer.UnitScanner = eof

@SethTisue SethTisue modified the milestones: 2.13.5, 2.13.6 Feb 9, 2021
@SethTisue
Copy link
Member Author

SethTisue commented May 6, 2021

for the part that involves interfacing with JLine, looking at Dotty's implementation should be helpful. according to https://github.com/lampepfl/dotty/blob/master/docs/blog/_posts/2017-10-16-fourth-dotty-milestone-release.md , “we use code adapted from the Ammonite REPL to provide syntax highlighting”, so maybe look there as well (but note that Ammonite uses Scalaparse which is a dependency we don't want to take on)

relevant source files include:

and files in dotty/tools/repl such as these and perhaps others:

note that SyntaxHighlighting.scala uses both dotc.parsing.Scanners.Scanner and dotc.parsing.Parsers.Parser but it isn't immediately obvious to me what proportion of each it uses or needs

The key snippets in JLineTerminal are:

    val lineReader = LineReaderBuilder
      ...
      .highlighter(new Highlighter)
      ...
  ...
  private class Highlighter(using Context) extends reader.Highlighter {
    def highlight(reader: LineReader, buffer: String): AttributedString = {
      val highlighted = SyntaxHighlighting.highlight(buffer)
      AttributedString.fromAnsi(highlighted)
    }
  ...

and I think that the use of SyntaxHighlighting in ReplDriver is to highlight definitions the REPL echoes back to the user, so e.g. the second line of this is syntax-highlighted too, not just the input:

scala> def inc(x: Int) = x + 1
def inc(x: Int): Int

@SethTisue
Copy link
Member Author

SethTisue commented May 6, 2021

I have some WIP at https://github.com/SethTisue/scala/commits/repl-highlighting

At present it only uses the scanner, not the parser. No tests yet.

It's a start...

@SethTisue
Copy link
Member Author

SethTisue commented May 7, 2021

How would using the parser look? Rather than talk to the parser ourselves, we should use the presentation compiler, which is designed for this sort of usage, and which the REPL already uses anyway, for doing tab completion. (That happens in ReplCompletion#codeCompletion, which calls intp.presentationCompile(cursor, buf) which returns a PresentationCompileResult giving access to the typed tree.)

Once we have a tree, we can copy the approach taken by SyntaxHighlighting in Dotty, which is to do highlighting in two passes. First it iterates over the tokens returned by the scanner and applies colors (as in the WIP code I've already pushed). Then it walks the trees using traverse and looking for node types of interest such as Ident or ValOrDefDef and applies more colors based on the position information attached to those nodes.

@SethTisue
Copy link
Member Author

SethTisue commented May 7, 2021

As Jason has noted, detecting comments needs special handling. In Dotty, Scanners offers a commentSpans method, backed by commentPosBuf internally and gated by a keepComments flag. I could backport that.

I wondered if there might be something comparable in the presentation compiler already, and, well, there sort of is: ScaladocScanner, which is currently specific to doc comments.

@dwijnand dwijnand modified the milestones: 2.13.6, 2.13.7 May 10, 2021
@SethTisue SethTisue modified the milestones: 2.13.7, 2.13.8 Oct 21, 2021
@SethTisue SethTisue modified the milestones: 2.13.8, 2.13.9 Dec 15, 2021
@SethTisue SethTisue modified the milestones: 2.13.9, 2.13.10 Apr 25, 2022
@SethTisue SethTisue removed this from the 2.13.10 milestone Sep 23, 2022
@SethTisue SethTisue added this to the Backlog milestone Sep 23, 2022
@SethTisue SethTisue changed the title REPL: add syntax highlighting, like the Dotty REPL (and Ammonite) REPL: add syntax highlighting, like the Scala 3 REPL (and Ammonite) Jan 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants