@@ -18,35 +18,51 @@ package org.pkl.cli.repl
1818import java.io.IOException
1919import java.net.URI
2020import java.nio.file.Path
21+ import java.util.regex.Pattern
2122import kotlin.io.path.deleteIfExists
2223import org.fusesource.jansi.Ansi
2324import org.jline.reader.EndOfFileException
25+ import org.jline.reader.Highlighter
26+ import org.jline.reader.LineReader
2427import org.jline.reader.LineReader.Option
2528import org.jline.reader.LineReaderBuilder
2629import org.jline.reader.UserInterruptException
2730import org.jline.reader.impl.completer.AggregateCompleter
2831import org.jline.reader.impl.history.DefaultHistory
2932import org.jline.terminal.TerminalBuilder
33+ import org.jline.utils.AttributedString
3034import org.jline.utils.InfoCmp
3135import org.pkl.core.repl.ReplRequest
3236import org.pkl.core.repl.ReplResponse
3337import org.pkl.core.repl.ReplServer
38+ import org.pkl.core.util.AnsiStringBuilder
39+ import org.pkl.core.util.AnsiStringBuilder.AnsiCode
3440import org.pkl.core.util.IoUtils
41+ import org.pkl.core.util.SyntaxHighlighter
3542
36- internal class Repl (workingDir : Path , private val server : ReplServer ) {
43+ class PklHighlighter : Highlighter {
44+ override fun highlight (reader : LineReader , buffer : String ): AttributedString {
45+ val ansi = AnsiStringBuilder (true ).apply { SyntaxHighlighter .writeTo(this , buffer) }.toString()
46+ return AttributedString .fromAnsi(ansi)
47+ }
48+
49+ override fun setErrorPattern (pattern : Pattern ) {}
50+
51+ override fun setErrorIndex (idx : Int ) {}
52+ }
53+
54+ internal class Repl (workingDir : Path , private val server : ReplServer , private val color : Boolean ) {
3755 private val terminal = TerminalBuilder .builder().apply { jansi(true ) }.build()
3856 private val history = DefaultHistory ()
3957 private val reader =
4058 LineReaderBuilder .builder()
4159 .apply {
4260 history(history)
4361 terminal(terminal)
62+ highlighter(PklHighlighter ())
4463 completer(AggregateCompleter (CommandCompleter , FileCompleter (workingDir)))
4564 option(Option .DISABLE_EVENT_EXPANSION , true )
46- variable(
47- org.jline.reader.LineReader .HISTORY_FILE ,
48- (IoUtils .getPklHomeDir().resolve(" repl-history" )),
49- )
65+ variable(LineReader .HISTORY_FILE , (IoUtils .getPklHomeDir().resolve(" repl-history" )))
5066 }
5167 .build()
5268
@@ -55,6 +71,12 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
5571 private var maybeQuit = false
5672 private var nextRequestId = 0
5773
74+ private fun String.faint (): String {
75+ val sb = AnsiStringBuilder (color)
76+ sb.append(AnsiCode .FAINT , this )
77+ return sb.toString()
78+ }
79+
5880 fun run () {
5981 // JLine 2 history file is incompatible with JLine 3
6082 IoUtils .getPklHomeDir().resolve(" repl-history.bin" ).deleteIfExists()
@@ -70,11 +92,11 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
7092 try {
7193 if (continuation) {
7294 nextRequestId - = 1
73- reader.readLine(" " .repeat(" pkl$nextRequestId > " .length))
95+ reader.readLine(" " .repeat(" pkl$nextRequestId > " .length).faint() )
7496 } else {
75- reader.readLine(" pkl$nextRequestId > " )
97+ reader.readLine(" pkl$nextRequestId > " .faint() )
7698 }
77- } catch (e : UserInterruptException ) {
99+ } catch (_ : UserInterruptException ) {
78100 if (! continuation && reader.buffer.length() == 0 ) {
79101 if (maybeQuit) quit()
80102 else {
@@ -87,7 +109,7 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
87109 inputBuffer = " "
88110 continuation = false
89111 continue
90- } catch (e : EndOfFileException ) {
112+ } catch (_ : EndOfFileException ) {
91113 " :quit"
92114 }
93115
@@ -111,10 +133,10 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
111133 } finally {
112134 try {
113135 history.save()
114- } catch (ignored : IOException ) {}
136+ } catch (_ : IOException ) {}
115137 try {
116138 terminal.close()
117- } catch (ignored : IOException ) {}
139+ } catch (_ : IOException ) {}
118140 }
119141 }
120142
@@ -124,10 +146,12 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
124146 candidates.isEmpty() -> {
125147 println (" Unknown command: `${inputBuffer.drop(1 )} `" )
126148 }
149+
127150 candidates.size > 1 -> {
128151 print (" Which of the following did you mean? " )
129152 println (candidates.joinToString(separator = " " ) { " `:${it.type} `" })
130153 }
154+
131155 else -> {
132156 doExecuteCommand(candidates.single())
133157 }
@@ -193,16 +217,20 @@ internal class Repl(workingDir: Path, private val server: ReplServer) {
193217 is ReplResponse .EvalSuccess -> {
194218 println (response.result)
195219 }
220+
196221 is ReplResponse .EvalError -> {
197222 println (response.message)
198223 }
224+
199225 is ReplResponse .InternalError -> {
200226 throw response.cause
201227 }
228+
202229 is ReplResponse .IncompleteInput -> {
203230 assert (responses.size == 1 )
204231 continuation = true
205232 }
233+
206234 else -> throw IllegalStateException (" Unexpected response: $response " )
207235 }
208236 }
0 commit comments