@@ -18,76 +18,61 @@ import org.jline.terminal.TerminalBuilder
1818import org .jline .utils .AttributedString
1919
2020class JLineTerminal extends java.io.Closeable {
21- // import java.util.logging.{Logger, Level}
22- // Logger.getLogger("org.jline").setLevel(Level.FINEST)
2321
2422 private val terminal =
2523 var builder = TerminalBuilder .builder()
2624 if System .getenv(" TERM" ) == " dumb" then
27- // Force dumb terminal if `TERM` is `"dumb"`.
28- // Note: the default value for the `dumb` option is `null`, which allows
29- // JLine to fall back to a dumb terminal. This is different than `true` or
30- // `false` and can't be set using the `dumb` setter.
31- // This option is used at https://github.com/jline/jline3/blob/894b5e72cde28a551079402add4caea7f5527806/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java#L528.
3225 builder.dumb(true )
3326 builder.build()
27+
3428 private val history = new DefaultHistory
3529
3630 private def blue (str : String )(using Context ) =
3731 if (ctx.settings.color.value != " never" ) Console .BLUE + str + Console .RESET
3832 else str
33+
3934 protected def promptStr = " scala"
4035 private def prompt (using Context ) = blue(s " \n $promptStr> " )
4136 private def newLinePrompt (using Context ) = " "
4237
43- /** Blockingly read line from `System.in`
44- *
45- * This entry point into JLine handles everything to do with terminal
46- * emulation. This includes:
47- *
48- * - Multi-line support
49- * - Copy-pasting
50- * - History
51- * - Syntax highlighting
52- * - Auto-completions
53- *
54- * @throws EndOfFileException This exception is thrown when the user types Ctrl-D.
55- */
5638 def readLine (
57- completer : Completer // provide auto-completions
39+ completer : Completer
5840 )(using Context ): String = {
5941 import LineReader .Option .*
6042 import LineReader .*
6143 val userHome = System .getProperty(" user.home" )
44+
6245 val lineReader = LineReaderBuilder
6346 .builder()
6447 .terminal(terminal)
6548 .history(history)
6649 .completer(completer)
6750 .highlighter(new Highlighter )
6851 .parser(new Parser )
69- .variable(HISTORY_FILE , s " $userHome/.dotty_history " ) // Save history to file
70- .variable(SECONDARY_PROMPT_PATTERN , " %M" ) // A short word explaining what is "missing",
71- // this is supplied from the EOFError.getMissing() method
72- .variable(LIST_MAX , 400 ) // Ask user when number of completions exceed this limit (default is 100).
73- .variable(BLINK_MATCHING_PAREN , 0L ) // Don't blink the opening paren after typing a closing paren.
74- .variable(WORDCHARS ,
75- LineReaderImpl .DEFAULT_WORDCHARS .filterNot(" *?.[]~=/&;!#%^(){}<>" .toSet)) // Finer grained word boundaries
76- .option(INSERT_TAB , true ) // At the beginning of the line, insert tab instead of completing.
77- .option(AUTO_FRESH_LINE , true ) // if not at start of line before prompt, move to new line.
78- .option(DISABLE_EVENT_EXPANSION , true ) // don't process escape sequences in input
52+ .variable(HISTORY_FILE , s " $userHome/.dotty_history " )
53+ .variable(SECONDARY_PROMPT_PATTERN , " %M" )
54+ .variable(LIST_MAX , 400 )
55+ .variable(BLINK_MATCHING_PAREN , 0L )
56+ .variable(
57+ WORDCHARS ,
58+ LineReaderImpl .DEFAULT_WORDCHARS .filterNot(" *?.[]~=/&;!#%^(){}<>" .toSet)
59+ )
60+ .option(INSERT_TAB , true )
61+ .option(AUTO_FRESH_LINE , true )
62+ .option(DISABLE_EVENT_EXPANSION , true )
7963 .build()
8064
8165 lineReader.readLine(prompt)
8266 }
8367
8468 def close (): Unit = terminal.close()
8569
86- /** Register a signal handler and return the previous handler */
87- def handle (signal : org.jline.terminal.Terminal .Signal , handler : org.jline.terminal.Terminal .SignalHandler ): org.jline.terminal.Terminal .SignalHandler =
70+ def handle (
71+ signal : org.jline.terminal.Terminal .Signal ,
72+ handler : org.jline.terminal.Terminal .SignalHandler
73+ ): org.jline.terminal.Terminal .SignalHandler =
8874 terminal.handle(signal, handler)
8975
90- /** Provide syntax highlighting */
9176 private class Highlighter (using Context ) extends reader.Highlighter {
9277 def highlight (reader : LineReader , buffer : String ): AttributedString = {
9378 val highlighted = SyntaxHighlighting .highlight(buffer)
@@ -97,39 +82,35 @@ class JLineTerminal extends java.io.Closeable {
9782 def setErrorIndex (errorIndex : Int ): Unit = {}
9883 }
9984
100- /** Provide multi-line editing support */
10185 private class Parser (using Context ) extends reader.Parser {
10286
103- /**
104- * @param cursor The cursor position within the line
105- * @param line The unparsed line
106- * @param word The current word being completed
107- * @param wordCursor The cursor position within the current word
108- */
10987 private class ParsedLine (
11088 val cursor : Int , val line : String , val word : String , val wordCursor : Int
11189 ) extends reader.ParsedLine {
112- // Using dummy values, not sure what they are used for
11390 def wordIndex = - 1
114- def words : java.util.List [String ] = java.util.Collections .emptyList[String ]
91+ def words : java.util.List [String ] =
92+ java.util.Collections .emptyList[String ]
11593 }
11694
117- def parse (input : String , cursor : Int , context : ParseContext ): reader.ParsedLine = {
95+ def parse (
96+ input : String ,
97+ cursor : Int ,
98+ context : ParseContext
99+ ): reader.ParsedLine = {
100+
118101 def parsedLine (word : String , wordCursor : Int ) =
119102 new ParsedLine (cursor, input, word, wordCursor)
120- // Used when no word is being completed
103+
121104 def defaultParsedLine = parsedLine(" " , 0 )
122105
123106 def incomplete (): Nothing = throw new EOFError (
124- // Using dummy values, not sure what they are used for
125- /* line = */ - 1 ,
126- /* column = */ - 1 ,
127- /* message = */ " " ,
128- /* missing = */ newLinePrompt)
107+ - 1 , - 1 , " " , newLinePrompt
108+ )
129109
130110 case class TokenData (token : Token , start : Int , end : Int )
131- def currentToken : TokenData /* | Null */ = {
132- val source = SourceFile .virtual(" <completions>" , input)
111+
112+ def currentToken : TokenData | Null = {
113+ val source = SourceFile .virtual(" <completions>" , input)
133114 val scanner = new Scanner (source)(using ctx.fresh.setReporter(Reporter .NoReporter ))
134115 var lastBacktickErrorStart : Option [Int ] = None
135116
@@ -139,12 +120,12 @@ class JLineTerminal extends java.io.Closeable {
139120 scanner.nextToken()
140121 val end = scanner.lastOffset
141122
142- val isCurrentToken = cursor >= start && cursor <= end
123+ val isCurrentToken =
124+ cursor >= start && cursor <= end
125+
143126 if (isCurrentToken)
144127 return TokenData (token, lastBacktickErrorStart.getOrElse(start), end)
145128
146-
147- // we need to enclose the last backtick, which unclosed produces ERROR token
148129 if (token == ERROR && input(start) == '`' ) then
149130 lastBacktickErrorStart = Some (start)
150131 else
@@ -153,39 +134,35 @@ class JLineTerminal extends java.io.Closeable {
153134 null
154135 }
155136
156- def acceptLine = {
157- val onLastLine = ! input.substring(cursor).contains(System .lineSeparator)
158- onLastLine && ! ParseResult .isIncomplete(input)
159- }
137+ def acceptLine =
138+ ! input.substring(cursor).contains(System .lineSeparator) &&
139+ ! ParseResult .isIncomplete(input)
160140
161141 context match {
162- case ParseContext .ACCEPT_LINE if acceptLine =>
163- // using dummy values, resulting parsed input is probably unused
164- defaultParsedLine
165-
166- // In the situation where we have a partial command that we want to
167- // complete we need to ensure that the :<partial-word> isn't split into
168- // 2 tokens, but rather the entire thing is treated as the "word", in
169- // order to insure the : is replaced in the completion.
170- case ParseContext .COMPLETE if
171- ParseResult .commands.exists(command => command._1.startsWith(input)) =>
172- parsedLine(input, cursor)
173-
174- case ParseContext .COMPLETE =>
175- // Parse to find completions (typically after a Tab).
176- def isCompletable (token : Token ) = isIdentifier(token) || isKeyword(token)
177- currentToken match {
178- case TokenData (token, start, end) if isCompletable(token) =>
179- val word = input.substring(start, end)
180- val wordCursor = cursor - start
181- parsedLine(word, wordCursor)
182- case _ =>
183- defaultParsedLine
184- }
185-
186- case _ =>
187- incomplete()
188- }
142+
143+ case ParseContext .ACCEPT_LINE if acceptLine =>
144+ defaultParsedLine
145+
146+ // FIX: REPL commands starting with ":" must be treated as one word
147+ case ParseContext .COMPLETE if input.startsWith(" :" ) =>
148+ parsedLine(input, cursor)
149+
150+ case ParseContext .COMPLETE =>
151+ def isCompletable (token : Token ) =
152+ isIdentifier(token) || isKeyword(token)
153+
154+ currentToken match
155+ case TokenData (token, start, end) if isCompletable(token) =>
156+ val word = input.substring(start, end)
157+ val wordCursor = cursor - start
158+ parsedLine(word, wordCursor)
159+ case _ =>
160+ defaultParsedLine
161+
162+ case _ =>
163+ incomplete()
164+ }
165+
189166 }
190167 }
191168}
0 commit comments