Tab-completion in REPL based on module variables.
This commit is contained in:
+48
-11
@@ -1,13 +1,6 @@
|
||||
import "meta" for Meta
|
||||
import "io" for Stdin
|
||||
|
||||
class EscapeBracket {
|
||||
static up { 65 }
|
||||
static down { 66 }
|
||||
static right { 67 }
|
||||
static left { 68 }
|
||||
}
|
||||
|
||||
class Repl {
|
||||
construct new() {
|
||||
_cursor = 0
|
||||
@@ -19,7 +12,7 @@ class Repl {
|
||||
|
||||
run() {
|
||||
Stdin.isRaw = true
|
||||
refreshLine()
|
||||
refreshLine(false)
|
||||
|
||||
while (true) {
|
||||
var byte = Stdin.readByte()
|
||||
@@ -32,7 +25,7 @@ class Repl {
|
||||
return
|
||||
} else if (byte == Chars.ctrlD) {
|
||||
// If the line is empty, Ctrl_D exits.
|
||||
if (!_line.isEmpty) {
|
||||
if (_line.isEmpty) {
|
||||
System.print()
|
||||
return
|
||||
}
|
||||
@@ -43,6 +36,12 @@ class Repl {
|
||||
_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]
|
||||
@@ -77,7 +76,7 @@ class Repl {
|
||||
System.print("Unhandled byte: %(byte)")
|
||||
}
|
||||
|
||||
refreshLine()
|
||||
refreshLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +156,9 @@ class Repl {
|
||||
}
|
||||
|
||||
executeInput() {
|
||||
// Remove the completion hint.
|
||||
refreshLine(false)
|
||||
|
||||
// Add it to the history.
|
||||
_history.add(_line)
|
||||
_historyIndex = _history.count
|
||||
@@ -191,12 +193,14 @@ class Repl {
|
||||
var fiber
|
||||
if (isStatement) {
|
||||
fiber = Fiber.new {
|
||||
// TODO: Should evaluate in main module, not repl's own.
|
||||
Meta.eval(input)
|
||||
}
|
||||
|
||||
var result = fiber.try()
|
||||
if (fiber.error == null) return
|
||||
} else {
|
||||
// TODO: Should evaluate in main module, not repl's own.
|
||||
var function = Meta.compileExpression(input)
|
||||
if (function == null) return
|
||||
|
||||
@@ -204,6 +208,9 @@ 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)")
|
||||
return
|
||||
}
|
||||
@@ -229,7 +236,7 @@ class Repl {
|
||||
return tokens
|
||||
}
|
||||
|
||||
refreshLine() {
|
||||
refreshLine(showCompletion) {
|
||||
// Erase the whole line.
|
||||
System.write("\x1b[2K")
|
||||
|
||||
@@ -247,9 +254,32 @@ class Repl {
|
||||
System.write(Color.none)
|
||||
}
|
||||
|
||||
if (showCompletion) {
|
||||
var completion = getCompletion()
|
||||
if (completion != null) {
|
||||
System.write("%(Color.gray)%(completion)%(Color.none)")
|
||||
}
|
||||
}
|
||||
|
||||
// Position the cursor.
|
||||
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
|
||||
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ANSI color escape sequences.
|
||||
@@ -351,6 +381,13 @@ class Chars {
|
||||
static isWhitespace(c) { c == space || c == tab || c == carriageReturn }
|
||||
}
|
||||
|
||||
class EscapeBracket {
|
||||
static up { 0x41 }
|
||||
static down { 0x42 }
|
||||
static right { 0x43 }
|
||||
static left { 0x44 }
|
||||
}
|
||||
|
||||
class Token {
|
||||
// Punctuators.
|
||||
static leftParen { "leftParen" }
|
||||
|
||||
+48
-12
@@ -3,13 +3,6 @@ static const char* replModuleSource =
|
||||
"import \"meta\" for Meta\n"
|
||||
"import \"io\" for Stdin\n"
|
||||
"\n"
|
||||
"class EscapeBracket {\n"
|
||||
" static up { 65 }\n"
|
||||
" static down { 66 }\n"
|
||||
" static right { 67 }\n"
|
||||
" static left { 68 }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Repl {\n"
|
||||
" construct new() {\n"
|
||||
" _cursor = 0\n"
|
||||
@@ -21,7 +14,7 @@ static const char* replModuleSource =
|
||||
"\n"
|
||||
" run() {\n"
|
||||
" Stdin.isRaw = true\n"
|
||||
" refreshLine()\n"
|
||||
" refreshLine(false)\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var byte = Stdin.readByte()\n"
|
||||
@@ -34,7 +27,7 @@ static const char* replModuleSource =
|
||||
" return\n"
|
||||
" } else if (byte == Chars.ctrlD) {\n"
|
||||
" // If the line is empty, Ctrl_D exits.\n"
|
||||
" if (!_line.isEmpty) {\n"
|
||||
" if (_line.isEmpty) {\n"
|
||||
" System.print()\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
@@ -45,6 +38,12 @@ static const char* replModuleSource =
|
||||
" _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"
|
||||
@@ -73,14 +72,13 @@ static const char* replModuleSource =
|
||||
" // 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-L clear screen.\n"
|
||||
" // TODO: Ctrl-W delete previous word.\n"
|
||||
" // TODO: Completion.\n"
|
||||
" // TODO: Other shortcuts?\n"
|
||||
" System.print(\"Unhandled byte: %(byte)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" refreshLine()\n"
|
||||
" refreshLine(true)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
@@ -160,6 +158,9 @@ static const char* replModuleSource =
|
||||
" }\n"
|
||||
"\n"
|
||||
" executeInput() {\n"
|
||||
" // Remove the completion hint.\n"
|
||||
" refreshLine(false)\n"
|
||||
"\n"
|
||||
" // Add it to the history.\n"
|
||||
" _history.add(_line)\n"
|
||||
" _historyIndex = _history.count\n"
|
||||
@@ -194,12 +195,14 @@ static const char* replModuleSource =
|
||||
" var fiber\n"
|
||||
" if (isStatement) {\n"
|
||||
" fiber = Fiber.new {\n"
|
||||
" // TODO: Should evaluate in main module, not repl's own.\n"
|
||||
" Meta.eval(input)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var result = fiber.try()\n"
|
||||
" if (fiber.error == null) return\n"
|
||||
" } else {\n"
|
||||
" // TODO: Should evaluate in main module, not repl's own.\n"
|
||||
" var function = Meta.compileExpression(input)\n"
|
||||
" if (function == null) return\n"
|
||||
"\n"
|
||||
@@ -207,6 +210,9 @@ 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"
|
||||
" return\n"
|
||||
" }\n"
|
||||
@@ -232,7 +238,7 @@ static const char* replModuleSource =
|
||||
" return tokens\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" refreshLine() {\n"
|
||||
" refreshLine(showCompletion) {\n"
|
||||
" // Erase the whole line.\n"
|
||||
" System.write(\"\x1b[2K\")\n"
|
||||
"\n"
|
||||
@@ -250,9 +256,32 @@ static const char* replModuleSource =
|
||||
" System.write(Color.none)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (showCompletion) {\n"
|
||||
" var completion = getCompletion()\n"
|
||||
" if (completion != null) {\n"
|
||||
" System.write(\"%(Color.gray)%(completion)%(Color.none)\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Position the cursor.\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"
|
||||
"\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"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"/// ANSI color escape sequences.\n"
|
||||
@@ -354,6 +383,13 @@ static const char* replModuleSource =
|
||||
" static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class EscapeBracket {\n"
|
||||
" static up { 0x41 }\n"
|
||||
" static down { 0x42 }\n"
|
||||
" static right { 0x43 }\n"
|
||||
" static left { 0x44 }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Token {\n"
|
||||
" // Punctuators.\n"
|
||||
" static leftParen { \"leftParen\" }\n"
|
||||
|
||||
Reference in New Issue
Block a user