forked from Mirror/wren
Fall back to a simpler REPL if it seems like ANSI escapes won't work.
This commit is contained in:
@ -1,6 +1,9 @@
|
||||
import "meta" for Meta
|
||||
import "io" for Stdin
|
||||
import "os" for Platform
|
||||
|
||||
/// Abstract base class for the REPL. Manages the input line and history, but
|
||||
/// does not render.
|
||||
class Repl {
|
||||
construct new() {
|
||||
_cursor = 0
|
||||
@ -8,78 +11,79 @@ class Repl {
|
||||
|
||||
_history = []
|
||||
_historyIndex = 0
|
||||
|
||||
// Whether or not we allow ASCI escape sequences for showing colors and
|
||||
// moving the cursor. When this is false, the REPL has reduced
|
||||
// functionality.
|
||||
_allowAnsi = false
|
||||
}
|
||||
|
||||
cursor { _cursor }
|
||||
cursor=(value) { _cursor = value }
|
||||
line { _line }
|
||||
line=(value) { _line = value }
|
||||
|
||||
run() {
|
||||
Stdin.isRaw = true
|
||||
refreshLine(false)
|
||||
|
||||
while (true) {
|
||||
var byte = Stdin.readByte()
|
||||
if (byte == Chars.ctrlA) {
|
||||
_cursor = 0
|
||||
} else if (byte == Chars.ctrlB) {
|
||||
cursorLeft()
|
||||
} else if (byte == Chars.ctrlC) {
|
||||
System.print()
|
||||
return
|
||||
} else if (byte == Chars.ctrlD) {
|
||||
// If the line is empty, Ctrl_D exits.
|
||||
if (_line.isEmpty) {
|
||||
System.print()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, it deletes the character after the cursor.
|
||||
deleteRight()
|
||||
} else if (byte == Chars.ctrlE) {
|
||||
_cursor = _line.count
|
||||
} else if (byte == Chars.ctrlF) {
|
||||
cursorRight()
|
||||
} else if (byte == Chars.tab) {
|
||||
var completion = getCompletion()
|
||||
if (completion != null) {
|
||||
_line = _line + completion
|
||||
_cursor = _line.count
|
||||
}
|
||||
} else if (byte == Chars.ctrlK) {
|
||||
// Delete everything after the cursor.
|
||||
_line = _line[0..._cursor]
|
||||
} else if (byte == Chars.ctrlL) {
|
||||
// Clear the screen.
|
||||
System.write("\x1b[2J")
|
||||
// Move cursor to top left.
|
||||
System.write("\x1b[H")
|
||||
} else if (byte == Chars.ctrlU) {
|
||||
// Clear the line.
|
||||
_line = ""
|
||||
_cursor = 0
|
||||
} else if (byte == Chars.ctrlN) {
|
||||
nextHistory()
|
||||
} else if (byte == Chars.ctrlP) {
|
||||
previousHistory()
|
||||
} else if (byte == Chars.escape) {
|
||||
handleEscape()
|
||||
} else if (byte == Chars.carriageReturn) {
|
||||
executeInput()
|
||||
} else if (byte == Chars.delete) {
|
||||
deleteLeft()
|
||||
} else if (byte >= Chars.space && byte <= Chars.tilde) {
|
||||
insertChar(byte)
|
||||
} else {
|
||||
// TODO: Ctrl-T to swap chars.
|
||||
// TODO: ESC H and F to move to beginning and end of line. (Both ESC
|
||||
// [ and ESC 0 sequences?)
|
||||
// TODO: Ctrl-W delete previous word.
|
||||
// TODO: Completion.
|
||||
// TODO: Other shortcuts?
|
||||
System.print("Unhandled byte: %(byte)")
|
||||
}
|
||||
|
||||
if (handleChar(byte)) break
|
||||
refreshLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
handleChar(byte) {
|
||||
if (byte == Chars.ctrlC) {
|
||||
System.print()
|
||||
return true
|
||||
} else if (byte == Chars.ctrlD) {
|
||||
// If the line is empty, Ctrl_D exits.
|
||||
if (_line.isEmpty) {
|
||||
System.print()
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise, it deletes the character after the cursor.
|
||||
deleteRight()
|
||||
} else if (byte == Chars.tab) {
|
||||
var completion = getCompletion()
|
||||
if (completion != null) {
|
||||
_line = _line + completion
|
||||
_cursor = _line.count
|
||||
}
|
||||
} else if (byte == Chars.ctrlU) {
|
||||
// Clear the line.
|
||||
_line = ""
|
||||
_cursor = 0
|
||||
} else if (byte == Chars.ctrlN) {
|
||||
nextHistory()
|
||||
} else if (byte == Chars.ctrlP) {
|
||||
previousHistory()
|
||||
} else if (byte == Chars.escape) {
|
||||
var escapeType = Stdin.readByte()
|
||||
var value = Stdin.readByte()
|
||||
if (escapeType == Chars.leftBracket) {
|
||||
// ESC [ sequence.
|
||||
handleEscapeBracket(value)
|
||||
} else {
|
||||
// TODO: Handle ESC 0 sequences.
|
||||
}
|
||||
} else if (byte == Chars.carriageReturn) {
|
||||
executeInput()
|
||||
} else if (byte == Chars.delete) {
|
||||
deleteLeft()
|
||||
} else if (byte >= Chars.space && byte <= Chars.tilde) {
|
||||
insertChar(byte)
|
||||
} else {
|
||||
// TODO: Other shortcuts?
|
||||
System.print("Unhandled byte: %(byte)")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// Inserts the character with [byte] value at the current cursor position.
|
||||
insertChar(byte) {
|
||||
var char = String.fromCodePoint(byte)
|
||||
@ -104,36 +108,14 @@ class Repl {
|
||||
_line = _line[0..._cursor] + _line[(_cursor + 1)..-1]
|
||||
}
|
||||
|
||||
handleEscape() {
|
||||
var escapeType = Stdin.readByte()
|
||||
var value = Stdin.readByte()
|
||||
if (escapeType == Chars.leftBracket) {
|
||||
// ESC [ sequence.
|
||||
if (value == EscapeBracket.up) {
|
||||
previousHistory()
|
||||
} else if (value == EscapeBracket.down) {
|
||||
nextHistory()
|
||||
} else if (value == EscapeBracket.left) {
|
||||
cursorLeft()
|
||||
} else if (value == EscapeBracket.right) {
|
||||
cursorRight()
|
||||
}
|
||||
} else {
|
||||
// TODO: Handle ESC 0 sequences.
|
||||
handleEscapeBracket(byte) {
|
||||
if (byte == EscapeBracket.up) {
|
||||
previousHistory()
|
||||
} else if (byte == EscapeBracket.down) {
|
||||
nextHistory()
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the cursor left one character.
|
||||
cursorLeft() {
|
||||
if (_cursor > 0) _cursor = _cursor - 1
|
||||
}
|
||||
|
||||
/// Move the cursor right one character.
|
||||
cursorRight() {
|
||||
// TODO: Take into account multi-byte characters?
|
||||
if (_cursor < _line.count) _cursor = _cursor + 1
|
||||
}
|
||||
|
||||
previousHistory() {
|
||||
if (_historyIndex == 0) return
|
||||
|
||||
@ -159,9 +141,11 @@ class Repl {
|
||||
// Remove the completion hint.
|
||||
refreshLine(false)
|
||||
|
||||
// Add it to the history.
|
||||
_history.add(_line)
|
||||
_historyIndex = _history.count
|
||||
// Add it to the history (if the line is interesting).
|
||||
if (_line != "" && (_history.isEmpty || _history[-1] != _line)) {
|
||||
_history.add(_line)
|
||||
_historyIndex = _history.count
|
||||
}
|
||||
|
||||
// Reset the current line.
|
||||
var input = _line
|
||||
@ -208,16 +192,13 @@ class Repl {
|
||||
var result = fiber.try()
|
||||
if (fiber.error == null) {
|
||||
// TODO: Handle error in result.toString.
|
||||
// TODO: Syntax color based on type? It might be nice to distinguish
|
||||
// between string results versus stringified results. Otherwise, the
|
||||
// user can't tell the difference between `true` and "true".
|
||||
System.print("%(Color.brightWhite)%(result)%(Color.none)")
|
||||
showResult(result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
System.print("%(Color.red)Runtime error: %(result)%(Color.none)")
|
||||
// TODO: Print entire stack.
|
||||
// TODO: Include callstack.
|
||||
showRuntimeError("Runtime error: %(fiber.error)")
|
||||
}
|
||||
|
||||
lex(line, includeWhitespace) {
|
||||
@ -236,6 +217,113 @@ class Repl {
|
||||
return tokens
|
||||
}
|
||||
|
||||
/// Gets the best possible auto-completion for the current line, or null if
|
||||
/// there is none. The completion is the remaining string to append to the
|
||||
/// line, not the entire completed line.
|
||||
getCompletion() {
|
||||
if (_line.isEmpty) return null
|
||||
|
||||
// Only complete if the cursor is at the end.
|
||||
if (_cursor != _line.count) return null
|
||||
|
||||
for (name in Meta.getModuleVariables("repl")) {
|
||||
// TODO: Also allow completion if the line ends with an identifier but
|
||||
// has other stuff before it.
|
||||
if (name.startsWith(_line)) {
|
||||
return name[_line.count..-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reduced functionality REPL that doesn't use ANSI escape sequences.
|
||||
class SimpleRepl is Repl {
|
||||
construct new() {
|
||||
super()
|
||||
_erase = ""
|
||||
}
|
||||
|
||||
refreshLine(showCompletion) {
|
||||
// A carriage return just moves the cursor to the beginning of the line.
|
||||
// We have to erase it manually. Since we can't use ANSI escapes, and we
|
||||
// don't know how wide the terminal is, erase the longest line we've seen
|
||||
// so far.
|
||||
if (line.count > _erase.count) _erase = " " * line.count
|
||||
System.write("\r %(_erase)")
|
||||
|
||||
// Show the prompt at the beginning of the line.
|
||||
System.write("\r> ")
|
||||
|
||||
// Write the line.
|
||||
System.write(line)
|
||||
}
|
||||
|
||||
showResult(value) {
|
||||
// TODO: Syntax color based on type? It might be nice to distinguish
|
||||
// between string results versus stringified results. Otherwise, the
|
||||
// user can't tell the difference between `true` and "true".
|
||||
System.print(value)
|
||||
}
|
||||
|
||||
showRuntimeError(message) {
|
||||
System.print(message)
|
||||
}
|
||||
}
|
||||
|
||||
class AnsiRepl is Repl {
|
||||
construct new() {
|
||||
super()
|
||||
}
|
||||
|
||||
handleChar(byte) {
|
||||
if (byte == Chars.ctrlA) {
|
||||
cursor = 0
|
||||
} else if (byte == Chars.ctrlB) {
|
||||
cursorLeft()
|
||||
} else if (byte == Chars.ctrlE) {
|
||||
cursor = line.count
|
||||
} else if (byte == Chars.ctrlF) {
|
||||
cursorRight()
|
||||
} else if (byte == Chars.ctrlK) {
|
||||
// Delete everything after the cursor.
|
||||
line = line[0...cursor]
|
||||
} else if (byte == Chars.ctrlL) {
|
||||
// Clear the screen.
|
||||
System.write("\x1b[2J")
|
||||
// Move cursor to top left.
|
||||
System.write("\x1b[H")
|
||||
} else {
|
||||
// TODO: Ctrl-T to swap chars.
|
||||
// TODO: ESC H and F to move to beginning and end of line. (Both ESC
|
||||
// [ and ESC 0 sequences?)
|
||||
// TODO: Ctrl-W delete previous word.
|
||||
return super.handleChar(byte)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
handleEscapeBracket(byte) {
|
||||
if (byte == EscapeBracket.left) {
|
||||
cursorLeft()
|
||||
} else if (byte == EscapeBracket.right) {
|
||||
cursorRight()
|
||||
}
|
||||
|
||||
super.handleEscapeBracket(byte)
|
||||
}
|
||||
|
||||
/// Move the cursor left one character.
|
||||
cursorLeft() {
|
||||
if (cursor > 0) cursor = cursor - 1
|
||||
}
|
||||
|
||||
/// Move the cursor right one character.
|
||||
cursorRight() {
|
||||
// TODO: Take into account multi-byte characters?
|
||||
if (cursor < line.count) cursor = cursor + 1
|
||||
}
|
||||
|
||||
refreshLine(showCompletion) {
|
||||
// Erase the whole line.
|
||||
System.write("\x1b[2K")
|
||||
@ -246,7 +334,7 @@ class Repl {
|
||||
System.write(Color.none)
|
||||
|
||||
// Syntax highlight the line.
|
||||
for (token in lex(_line, true)) {
|
||||
for (token in lex(line, true)) {
|
||||
if (token.type == Token.eof) break
|
||||
|
||||
System.write(TOKEN_COLORS[token.type])
|
||||
@ -262,23 +350,19 @@ class Repl {
|
||||
}
|
||||
|
||||
// Position the cursor.
|
||||
System.write("\r\x1b[%(2 + _cursor)C")
|
||||
System.write("\r\x1b[%(2 + cursor)C")
|
||||
}
|
||||
|
||||
/// Gets the best possible auto-completion for the current line, or null if
|
||||
/// there is none. The completion is the remaining string to append to the
|
||||
/// line, not the entire completed line.
|
||||
getCompletion() {
|
||||
if (_line.isEmpty) return null
|
||||
showResult(value) {
|
||||
// TODO: Syntax color based on type? It might be nice to distinguish
|
||||
// between string results versus stringified results. Otherwise, the
|
||||
// user can't tell the difference between `true` and "true".
|
||||
System.print("%(Color.brightWhite)%(value)%(Color.none)")
|
||||
}
|
||||
|
||||
// Only complete if the cursor is at the end.
|
||||
if (_cursor != _line.count) return null
|
||||
|
||||
for (name in Meta.getModuleVariables("repl")) {
|
||||
if (name.startsWith(_line)) {
|
||||
return name[_line.count..-1]
|
||||
}
|
||||
}
|
||||
showRuntimeError(message) {
|
||||
System.print("%(Color.red)%(message)%(Color.none)")
|
||||
// TODO: Print entire stack.
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,5 +920,10 @@ class Lexer {
|
||||
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
|
||||
}
|
||||
|
||||
// Fire up the REPL.
|
||||
Repl.new().run()
|
||||
// Fire up the REPL. We use ANSI when talking to a POSIX TTY.
|
||||
if (Platform.isPosix && Stdin.isTerminal) {
|
||||
AnsiRepl.new().run()
|
||||
} else {
|
||||
// ANSI escape sequences probably aren't supported, so degrade.
|
||||
SimpleRepl.new().run()
|
||||
}
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
static const char* replModuleSource =
|
||||
"import \"meta\" for Meta\n"
|
||||
"import \"io\" for Stdin\n"
|
||||
"import \"os\" for Platform\n"
|
||||
"\n"
|
||||
"/// Abstract base class for the REPL. Manages the input line and history, but\n"
|
||||
"/// does not render.\n"
|
||||
"class Repl {\n"
|
||||
" construct new() {\n"
|
||||
" _cursor = 0\n"
|
||||
@ -10,78 +13,79 @@ static const char* replModuleSource =
|
||||
"\n"
|
||||
" _history = []\n"
|
||||
" _historyIndex = 0\n"
|
||||
"\n"
|
||||
" // Whether or not we allow ASCI escape sequences for showing colors and\n"
|
||||
" // moving the cursor. When this is false, the REPL has reduced\n"
|
||||
" // functionality.\n"
|
||||
" _allowAnsi = false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" cursor { _cursor }\n"
|
||||
" cursor=(value) { _cursor = value }\n"
|
||||
" line { _line }\n"
|
||||
" line=(value) { _line = value }\n"
|
||||
"\n"
|
||||
" run() {\n"
|
||||
" Stdin.isRaw = true\n"
|
||||
" refreshLine(false)\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var byte = Stdin.readByte()\n"
|
||||
" if (byte == Chars.ctrlA) {\n"
|
||||
" _cursor = 0\n"
|
||||
" } else if (byte == Chars.ctrlB) {\n"
|
||||
" cursorLeft()\n"
|
||||
" } else if (byte == Chars.ctrlC) {\n"
|
||||
" System.print()\n"
|
||||
" return\n"
|
||||
" } else if (byte == Chars.ctrlD) {\n"
|
||||
" // If the line is empty, Ctrl_D exits.\n"
|
||||
" if (_line.isEmpty) {\n"
|
||||
" System.print()\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Otherwise, it deletes the character after the cursor.\n"
|
||||
" deleteRight()\n"
|
||||
" } else if (byte == Chars.ctrlE) {\n"
|
||||
" _cursor = _line.count\n"
|
||||
" } else if (byte == Chars.ctrlF) {\n"
|
||||
" cursorRight()\n"
|
||||
" } else if (byte == Chars.tab) {\n"
|
||||
" var completion = getCompletion()\n"
|
||||
" if (completion != null) {\n"
|
||||
" _line = _line + completion\n"
|
||||
" _cursor = _line.count\n"
|
||||
" }\n"
|
||||
" } else if (byte == Chars.ctrlK) {\n"
|
||||
" // Delete everything after the cursor.\n"
|
||||
" _line = _line[0..._cursor]\n"
|
||||
" } else if (byte == Chars.ctrlL) {\n"
|
||||
" // Clear the screen.\n"
|
||||
" System.write(\"\x1b[2J\")\n"
|
||||
" // Move cursor to top left.\n"
|
||||
" System.write(\"\x1b[H\")\n"
|
||||
" } else if (byte == Chars.ctrlU) {\n"
|
||||
" // Clear the line.\n"
|
||||
" _line = \"\"\n"
|
||||
" _cursor = 0\n"
|
||||
" } else if (byte == Chars.ctrlN) {\n"
|
||||
" nextHistory()\n"
|
||||
" } else if (byte == Chars.ctrlP) {\n"
|
||||
" previousHistory()\n"
|
||||
" } else if (byte == Chars.escape) {\n"
|
||||
" handleEscape()\n"
|
||||
" } else if (byte == Chars.carriageReturn) {\n"
|
||||
" executeInput()\n"
|
||||
" } else if (byte == Chars.delete) {\n"
|
||||
" deleteLeft()\n"
|
||||
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
|
||||
" insertChar(byte)\n"
|
||||
" } else {\n"
|
||||
" // TODO: Ctrl-T to swap chars.\n"
|
||||
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
|
||||
" // [ and ESC 0 sequences?)\n"
|
||||
" // TODO: Ctrl-W delete previous word.\n"
|
||||
" // TODO: Completion.\n"
|
||||
" // TODO: Other shortcuts?\n"
|
||||
" System.print(\"Unhandled byte: %(byte)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (handleChar(byte)) break\n"
|
||||
" refreshLine(true)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" handleChar(byte) {\n"
|
||||
" if (byte == Chars.ctrlC) {\n"
|
||||
" System.print()\n"
|
||||
" return true\n"
|
||||
" } else if (byte == Chars.ctrlD) {\n"
|
||||
" // If the line is empty, Ctrl_D exits.\n"
|
||||
" if (_line.isEmpty) {\n"
|
||||
" System.print()\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Otherwise, it deletes the character after the cursor.\n"
|
||||
" deleteRight()\n"
|
||||
" } else if (byte == Chars.tab) {\n"
|
||||
" var completion = getCompletion()\n"
|
||||
" if (completion != null) {\n"
|
||||
" _line = _line + completion\n"
|
||||
" _cursor = _line.count\n"
|
||||
" }\n"
|
||||
" } else if (byte == Chars.ctrlU) {\n"
|
||||
" // Clear the line.\n"
|
||||
" _line = \"\"\n"
|
||||
" _cursor = 0\n"
|
||||
" } else if (byte == Chars.ctrlN) {\n"
|
||||
" nextHistory()\n"
|
||||
" } else if (byte == Chars.ctrlP) {\n"
|
||||
" previousHistory()\n"
|
||||
" } else if (byte == Chars.escape) {\n"
|
||||
" var escapeType = Stdin.readByte()\n"
|
||||
" var value = Stdin.readByte()\n"
|
||||
" if (escapeType == Chars.leftBracket) {\n"
|
||||
" // ESC [ sequence.\n"
|
||||
" handleEscapeBracket(value)\n"
|
||||
" } else {\n"
|
||||
" // TODO: Handle ESC 0 sequences.\n"
|
||||
" }\n"
|
||||
" } else if (byte == Chars.carriageReturn) {\n"
|
||||
" executeInput()\n"
|
||||
" } else if (byte == Chars.delete) {\n"
|
||||
" deleteLeft()\n"
|
||||
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
|
||||
" insertChar(byte)\n"
|
||||
" } else {\n"
|
||||
" // TODO: Other shortcuts?\n"
|
||||
" System.print(\"Unhandled byte: %(byte)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Inserts the character with [byte] value at the current cursor position.\n"
|
||||
" insertChar(byte) {\n"
|
||||
" var char = String.fromCodePoint(byte)\n"
|
||||
@ -106,36 +110,14 @@ static const char* replModuleSource =
|
||||
" _line = _line[0..._cursor] + _line[(_cursor + 1)..-1]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" handleEscape() {\n"
|
||||
" var escapeType = Stdin.readByte()\n"
|
||||
" var value = Stdin.readByte()\n"
|
||||
" if (escapeType == Chars.leftBracket) {\n"
|
||||
" // ESC [ sequence.\n"
|
||||
" if (value == EscapeBracket.up) {\n"
|
||||
" previousHistory()\n"
|
||||
" } else if (value == EscapeBracket.down) {\n"
|
||||
" nextHistory()\n"
|
||||
" } else if (value == EscapeBracket.left) {\n"
|
||||
" cursorLeft()\n"
|
||||
" } else if (value == EscapeBracket.right) {\n"
|
||||
" cursorRight()\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" // TODO: Handle ESC 0 sequences.\n"
|
||||
" handleEscapeBracket(byte) {\n"
|
||||
" if (byte == EscapeBracket.up) {\n"
|
||||
" previousHistory()\n"
|
||||
" } else if (byte == EscapeBracket.down) {\n"
|
||||
" nextHistory()\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Move the cursor left one character.\n"
|
||||
" cursorLeft() {\n"
|
||||
" if (_cursor > 0) _cursor = _cursor - 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Move the cursor right one character.\n"
|
||||
" cursorRight() {\n"
|
||||
" // TODO: Take into account multi-byte characters?\n"
|
||||
" if (_cursor < _line.count) _cursor = _cursor + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" previousHistory() {\n"
|
||||
" if (_historyIndex == 0) return\n"
|
||||
"\n"
|
||||
@ -161,9 +143,11 @@ static const char* replModuleSource =
|
||||
" // Remove the completion hint.\n"
|
||||
" refreshLine(false)\n"
|
||||
"\n"
|
||||
" // Add it to the history.\n"
|
||||
" _history.add(_line)\n"
|
||||
" _historyIndex = _history.count\n"
|
||||
" // Add it to the history (if the line is interesting).\n"
|
||||
" if (_line != \"\" && (_history.isEmpty || _history[-1] != _line)) {\n"
|
||||
" _history.add(_line)\n"
|
||||
" _historyIndex = _history.count\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Reset the current line.\n"
|
||||
" var input = _line\n"
|
||||
@ -210,16 +194,13 @@ static const char* replModuleSource =
|
||||
" var result = fiber.try()\n"
|
||||
" if (fiber.error == null) {\n"
|
||||
" // TODO: Handle error in result.toString.\n"
|
||||
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
|
||||
" // between string results versus stringified results. Otherwise, the\n"
|
||||
" // user can't tell the difference between `true` and \"true\".\n"
|
||||
" System.print(\"%(Color.brightWhite)%(result)%(Color.none)\")\n"
|
||||
" showResult(result)\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" System.print(\"%(Color.red)Runtime error: %(result)%(Color.none)\")\n"
|
||||
" // TODO: Print entire stack.\n"
|
||||
" // TODO: Include callstack.\n"
|
||||
" showRuntimeError(\"Runtime error: %(fiber.error)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lex(line, includeWhitespace) {\n"
|
||||
@ -238,6 +219,113 @@ static const char* replModuleSource =
|
||||
" return tokens\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Gets the best possible auto-completion for the current line, or null if\n"
|
||||
" /// there is none. The completion is the remaining string to append to the\n"
|
||||
" /// line, not the entire completed line.\n"
|
||||
" getCompletion() {\n"
|
||||
" if (_line.isEmpty) return null\n"
|
||||
"\n"
|
||||
" // Only complete if the cursor is at the end.\n"
|
||||
" if (_cursor != _line.count) return null\n"
|
||||
"\n"
|
||||
" for (name in Meta.getModuleVariables(\"repl\")) {\n"
|
||||
" // TODO: Also allow completion if the line ends with an identifier but\n"
|
||||
" // has other stuff before it.\n"
|
||||
" if (name.startsWith(_line)) {\n"
|
||||
" return name[_line.count..-1]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"/// A reduced functionality REPL that doesn't use ANSI escape sequences.\n"
|
||||
"class SimpleRepl is Repl {\n"
|
||||
" construct new() {\n"
|
||||
" super()\n"
|
||||
" _erase = \"\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" refreshLine(showCompletion) {\n"
|
||||
" // A carriage return just moves the cursor to the beginning of the line.\n"
|
||||
" // We have to erase it manually. Since we can't use ANSI escapes, and we\n"
|
||||
" // don't know how wide the terminal is, erase the longest line we've seen\n"
|
||||
" // so far.\n"
|
||||
" if (line.count > _erase.count) _erase = \" \" * line.count\n"
|
||||
" System.write(\"\r %(_erase)\")\n"
|
||||
"\n"
|
||||
" // Show the prompt at the beginning of the line.\n"
|
||||
" System.write(\"\r> \")\n"
|
||||
"\n"
|
||||
" // Write the line.\n"
|
||||
" System.write(line)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" showResult(value) {\n"
|
||||
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
|
||||
" // between string results versus stringified results. Otherwise, the\n"
|
||||
" // user can't tell the difference between `true` and \"true\".\n"
|
||||
" System.print(value)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" showRuntimeError(message) {\n"
|
||||
" System.print(message)\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class AnsiRepl is Repl {\n"
|
||||
" construct new() {\n"
|
||||
" super()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" handleChar(byte) {\n"
|
||||
" if (byte == Chars.ctrlA) {\n"
|
||||
" cursor = 0\n"
|
||||
" } else if (byte == Chars.ctrlB) {\n"
|
||||
" cursorLeft()\n"
|
||||
" } else if (byte == Chars.ctrlE) {\n"
|
||||
" cursor = line.count\n"
|
||||
" } else if (byte == Chars.ctrlF) {\n"
|
||||
" cursorRight()\n"
|
||||
" } else if (byte == Chars.ctrlK) {\n"
|
||||
" // Delete everything after the cursor.\n"
|
||||
" line = line[0...cursor]\n"
|
||||
" } else if (byte == Chars.ctrlL) {\n"
|
||||
" // Clear the screen.\n"
|
||||
" System.write(\"\x1b[2J\")\n"
|
||||
" // Move cursor to top left.\n"
|
||||
" System.write(\"\x1b[H\")\n"
|
||||
" } else {\n"
|
||||
" // TODO: Ctrl-T to swap chars.\n"
|
||||
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
|
||||
" // [ and ESC 0 sequences?)\n"
|
||||
" // TODO: Ctrl-W delete previous word.\n"
|
||||
" return super.handleChar(byte)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" handleEscapeBracket(byte) {\n"
|
||||
" if (byte == EscapeBracket.left) {\n"
|
||||
" cursorLeft()\n"
|
||||
" } else if (byte == EscapeBracket.right) {\n"
|
||||
" cursorRight()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" super.handleEscapeBracket(byte)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Move the cursor left one character.\n"
|
||||
" cursorLeft() {\n"
|
||||
" if (cursor > 0) cursor = cursor - 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Move the cursor right one character.\n"
|
||||
" cursorRight() {\n"
|
||||
" // TODO: Take into account multi-byte characters?\n"
|
||||
" if (cursor < line.count) cursor = cursor + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" refreshLine(showCompletion) {\n"
|
||||
" // Erase the whole line.\n"
|
||||
" System.write(\"\x1b[2K\")\n"
|
||||
@ -248,7 +336,7 @@ static const char* replModuleSource =
|
||||
" System.write(Color.none)\n"
|
||||
"\n"
|
||||
" // Syntax highlight the line.\n"
|
||||
" for (token in lex(_line, true)) {\n"
|
||||
" for (token in lex(line, true)) {\n"
|
||||
" if (token.type == Token.eof) break\n"
|
||||
"\n"
|
||||
" System.write(TOKEN_COLORS[token.type])\n"
|
||||
@ -264,23 +352,19 @@ static const char* replModuleSource =
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Position the cursor.\n"
|
||||
" System.write(\"\r\x1b[%(2 + _cursor)C\")\n"
|
||||
" System.write(\"\r\x1b[%(2 + cursor)C\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// Gets the best possible auto-completion for the current line, or null if\n"
|
||||
" /// there is none. The completion is the remaining string to append to the\n"
|
||||
" /// line, not the entire completed line.\n"
|
||||
" getCompletion() {\n"
|
||||
" if (_line.isEmpty) return null\n"
|
||||
" showResult(value) {\n"
|
||||
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
|
||||
" // between string results versus stringified results. Otherwise, the\n"
|
||||
" // user can't tell the difference between `true` and \"true\".\n"
|
||||
" System.print(\"%(Color.brightWhite)%(value)%(Color.none)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Only complete if the cursor is at the end.\n"
|
||||
" if (_cursor != _line.count) return null\n"
|
||||
"\n"
|
||||
" for (name in Meta.getModuleVariables(\"repl\")) {\n"
|
||||
" if (name.startsWith(_line)) {\n"
|
||||
" return name[_line.count..-1]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" showRuntimeError(message) {\n"
|
||||
" System.print(\"%(Color.red)%(message)%(Color.none)\")\n"
|
||||
" // TODO: Print entire stack.\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
@ -838,5 +922,10 @@ static const char* replModuleSource =
|
||||
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"// Fire up the REPL.\n"
|
||||
"Repl.new().run()\n";
|
||||
"// Fire up the REPL. We use ANSI when talking to a POSIX TTY.\n"
|
||||
"if (Platform.isPosix && Stdin.isTerminal) {\n"
|
||||
" AnsiRepl.new().run()\n"
|
||||
"} else {\n"
|
||||
" // ANSI escape sequences probably aren't supported, so degrade.\n"
|
||||
" SimpleRepl.new().run()\n"
|
||||
"}\n";
|
||||
|
||||
@ -46,7 +46,7 @@ void metaGetModuleVariables(WrenVM* vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjModule* module = AS_MODULE(moduleValue);
|
||||
ObjModule* module = AS_MODULE(moduleValue);
|
||||
ObjList* names = wrenNewList(vm, module->variableNames.count);
|
||||
vm->apiStack[0] = OBJ_VAL(names);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user