This commit is contained in:
Adam D. Ruppe 2020-01-23 14:51:40 -05:00
commit bd7a3fcc32
3 changed files with 513 additions and 151 deletions

69
http2.d
View File

@ -318,8 +318,25 @@ import std.conv;
import std.range; import std.range;
private AddressFamily family(string unixSocketPath) {
if(unixSocketPath.length)
return AddressFamily.UNIX;
else // FIXME: what about ipv6?
return AddressFamily.INET;
}
// Copy pasta from cgi.d, then stripped down version(Windows)
private class UnixAddress : Address {
this(string) {
throw new Exception("No unix address support on this system in lib yet :(");
}
override sockaddr* name() { assert(0); }
override const(sockaddr)* name() const { assert(0); }
override int nameLen() const { assert(0); }
}
// Copy pasta from cgi.d, then stripped down. unix path thing added tho
/// ///
struct Uri { struct Uri {
alias toString this; // blargh idk a url really is a string, but should it be implicit? alias toString this; // blargh idk a url really is a string, but should it be implicit?
@ -339,6 +356,18 @@ struct Uri {
reparse(uri); reparse(uri);
} }
private string unixSocketPath = null;
/// Indicates it should be accessed through a unix socket instead of regular tcp
void viaUnixSocket(string path) {
unixSocketPath = path;
}
/// Goes through a unix socket in the abstract namespace (linux only)
version(linux)
void viaAbstractSocket(string path) {
unixSocketPath = "\0" ~ path;
}
private void reparse(string uri) { private void reparse(string uri) {
// from RFC 3986 // from RFC 3986
// the ctRegex triples the compile time and makes ugly errors for no real benefit // the ctRegex triples the compile time and makes ugly errors for no real benefit
@ -545,6 +574,11 @@ struct Uri {
n.removeDots(); n.removeDots();
// if still basically talking to the same thing, we should inherit the unix path
// too since basically the unix path is saying for this service, always use this override.
if(n.host == baseUrl.host && n.scheme == baseUrl.scheme && n.port == baseUrl.port)
n.unixSocketPath = baseUrl.unixSocketPath;
return n; return n;
} }
@ -630,6 +664,7 @@ class HttpRequest {
auto parts = where; auto parts = where;
finalUrl = where.toString(); finalUrl = where.toString();
requestParameters.method = method; requestParameters.method = method;
requestParameters.unixSocketPath = where.unixSocketPath;
requestParameters.host = parts.host; requestParameters.host = parts.host;
requestParameters.port = cast(ushort) parts.port; requestParameters.port = cast(ushort) parts.port;
requestParameters.ssl = parts.scheme == "https"; requestParameters.ssl = parts.scheme == "https";
@ -812,18 +847,25 @@ class HttpRequest {
} }
} }
Socket getOpenSocketOnHost(string host, ushort port, bool ssl) { Socket getOpenSocketOnHost(string host, ushort port, bool ssl, string unixSocketPath) {
Socket openNewConnection() { Socket openNewConnection() {
Socket socket; Socket socket;
if(ssl) { if(ssl) {
version(with_openssl) version(with_openssl)
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM); socket = new SslClientSocket(family(unixSocketPath), SocketType.STREAM);
else else
throw new Exception("SSL not compiled in"); throw new Exception("SSL not compiled in");
} else } else
socket = new Socket(AddressFamily.INET, SocketType.STREAM); socket = new Socket(family(unixSocketPath), SocketType.STREAM);
if(unixSocketPath) {
socket.connect(new UnixAddress(unixSocketPath));
} else {
// FIXME: i should prolly do ipv6 if available too.
socket.connect(new InternetAddress(host, port));
}
socket.connect(new InternetAddress(host, port));
debug(arsd_http2) writeln("opening to ", host, ":", port, " ", cast(void*) socket); debug(arsd_http2) writeln("opening to ", host, ":", port, " ", cast(void*) socket);
assert(socket.handle() !is socket_t.init); assert(socket.handle() !is socket_t.init);
return socket; return socket;
@ -903,7 +945,7 @@ class HttpRequest {
continue; continue;
} }
auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl); auto socket = getOpenSocketOnHost(pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl, pc.requestParameters.unixSocketPath);
if(socket !is null) { if(socket !is null) {
activeRequestOnSocket[socket] = pc; activeRequestOnSocket[socket] = pc;
@ -1384,6 +1426,8 @@ struct HttpRequestParameters {
string contentType; /// string contentType; ///
ubyte[] bodyData; /// ubyte[] bodyData; ///
string unixSocketPath;
} }
interface IHttpClient { interface IHttpClient {
@ -2209,11 +2253,11 @@ wss://echo.websocket.org
if(ssl) { if(ssl) {
version(with_openssl) version(with_openssl)
socket = new SslClientSocket(AddressFamily.INET, SocketType.STREAM); socket = new SslClientSocket(family(uri.unixSocketPath), SocketType.STREAM);
else else
throw new Exception("SSL not compiled in"); throw new Exception("SSL not compiled in");
} else } else
socket = new Socket(AddressFamily.INET, SocketType.STREAM); socket = new Socket(family(uri.unixSocketPath), SocketType.STREAM);
} }
@ -2222,7 +2266,10 @@ wss://echo.websocket.org
+/ +/
/// Group: foundational /// Group: foundational
void connect() { void connect() {
socket.connect(new InternetAddress(host, port)); // FIXME: ipv6 support... if(uri.unixSocketPath)
socket.connect(new UnixAddress(uri.unixSocketPath));
else
socket.connect(new InternetAddress(host, port)); // FIXME: ipv6 support...
// FIXME: websocket handshake could and really should be async too. // FIXME: websocket handshake could and really should be async too.
auto uri = this.uri.path.length ? this.uri.path : "/"; auto uri = this.uri.path.length ? this.uri.path : "/";
@ -2924,8 +2971,8 @@ private {
return needsMoreData(); return needsMoreData();
} }
msg.data = d[0 .. msg.realLength]; msg.data = d[0 .. cast(size_t) msg.realLength];
d = d[msg.realLength .. $]; d = d[cast(size_t) msg.realLength .. $];
if(msg.masked) { if(msg.masked) {
// let's just unmask it now // let's just unmask it now

View File

@ -8943,6 +8943,10 @@ version(X11) {
void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
// source x, source y // source x, source y
if(ix >= i.width) return;
if(iy >= i.height) return;
if(ix + w > i.width) w = i.width - ix;
if(iy + h > i.height) h = i.height - iy;
if(i.usingXshm) if(i.usingXshm)
XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
else else

View File

@ -185,7 +185,10 @@ version(Posix) {
// capabilities. // capabilities.
//version = Demo //version = Demo
version(Windows) { version(Windows)
version=Win32Console;
version(Win32Console) {
import core.sys.windows.windows; import core.sys.windows.windows;
private { private {
enum RED_BIT = 4; enum RED_BIT = 4;
@ -406,6 +409,8 @@ enum ConsoleInputFlags {
allInputEvents = 8|4|2, /// subscribe to all input events. Note: in previous versions, this also returned release events. It no longer does, use allInputEventsWithRelease if you want them. allInputEvents = 8|4|2, /// subscribe to all input events. Note: in previous versions, this also returned release events. It no longer does, use allInputEventsWithRelease if you want them.
allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events. allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events.
noEolWrap = 128,
} }
/// Defines how terminal output should be handled. /// Defines how terminal output should be handled.
@ -424,6 +429,13 @@ enum ForceOption {
alwaysSend = 1, /// always send the data, even if it doesn't seem necessary alwaysSend = 1, /// always send the data, even if it doesn't seem necessary
} }
///
enum TerminalCursor {
DEFAULT = 0, ///
insert = 1, ///
block = 2 ///
}
// we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces // we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces
/// Encapsulates the I/O capabilities of a terminal. /// Encapsulates the I/O capabilities of a terminal.
@ -436,22 +448,91 @@ struct Terminal {
@disable this(this); @disable this(this);
private ConsoleOutputType type; private ConsoleOutputType type;
private TerminalCursor currentCursor_;
version(Windows) private CONSOLE_CURSOR_INFO originalCursorInfo;
/++
Changes the current cursor.
+/
void cursor(TerminalCursor what, ForceOption force = ForceOption.automatic) {
if(force == ForceOption.neverSend) {
currentCursor_ = what;
return;
} else {
if(what != currentCursor_ || force == ForceOption.alwaysSend) {
currentCursor_ = what;
version(Win32Console) {
final switch(what) {
case TerminalCursor.DEFAULT:
SetConsoleCursorInfo(hConsole, &originalCursorInfo);
break;
case TerminalCursor.insert:
case TerminalCursor.block:
CONSOLE_CURSOR_INFO info;
GetConsoleCursorInfo(hConsole, &info);
info.dwSize = what == TerminalCursor.insert ? 1 : 100;
SetConsoleCursorInfo(hConsole, &info);
break;
}
} else {
final switch(what) {
case TerminalCursor.DEFAULT:
if(terminalInFamily("linux"))
writeStringRaw("\033[?0c");
else
writeStringRaw("\033[0 q");
break;
case TerminalCursor.insert:
if(terminalInFamily("linux"))
writeStringRaw("\033[?2c");
else if(terminalInFamily("xterm"))
writeStringRaw("\033[6 q");
else
writeStringRaw("\033[4 q");
break;
case TerminalCursor.block:
if(terminalInFamily("linux"))
writeStringRaw("\033[?6c");
else
writeStringRaw("\033[2 q");
break;
}
}
}
}
}
/++ /++
Terminal is only valid to use on an actual console device or terminal Terminal is only valid to use on an actual console device or terminal
handle. You should not attempt to construct a Terminal instance if this handle. You should not attempt to construct a Terminal instance if this
returns false; returns false. Real time input is similarly impossible if `!stdinIsTerminal`.
+/ +/
static bool stdoutIsTerminal() { static bool stdoutIsTerminal() {
version(Posix) { version(Posix) {
import core.sys.posix.unistd; import core.sys.posix.unistd;
return cast(bool) isatty(1); return cast(bool) isatty(1);
} else version(Windows) { } else version(Win32Console) {
auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
return GetFileType(hConsole) == FILE_TYPE_CHAR;
/+
auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE); auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO originalSbi; CONSOLE_SCREEN_BUFFER_INFO originalSbi;
if(GetConsoleScreenBufferInfo(hConsole, &originalSbi) == 0) if(GetConsoleScreenBufferInfo(hConsole, &originalSbi) == 0)
return false; return false;
else else
return true; return true;
+/
} else static assert(0);
}
///
static bool stdinIsTerminal() {
version(Posix) {
import core.sys.posix.unistd;
return cast(bool) isatty(0);
} else version(Win32Console) {
auto hConsole = GetStdHandle(STD_INPUT_HANDLE);
return GetFileType(hConsole) == FILE_TYPE_CHAR;
} else static assert(0); } else static assert(0);
} }
@ -782,12 +863,12 @@ struct Terminal {
} }
} }
version(Windows) { version(Win32Console) {
HANDLE hConsole; HANDLE hConsole;
CONSOLE_SCREEN_BUFFER_INFO originalSbi; CONSOLE_SCREEN_BUFFER_INFO originalSbi;
} }
version(Windows) version(Win32Console)
/// ditto /// ditto
this(ConsoleOutputType type) { this(ConsoleOutputType type) {
if(type == ConsoleOutputType.cellular) { if(type == ConsoleOutputType.cellular) {
@ -815,6 +896,8 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
//size.Y = 24; //size.Y = 24;
//SetConsoleScreenBufferSize(hConsole, size); //SetConsoleScreenBufferSize(hConsole, size);
GetConsoleCursorInfo(hConsole, &originalCursorInfo);
clear(); clear();
} else { } else {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
@ -838,7 +921,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
*/ */
} }
version(Windows) { version(Win32Console) {
private Color defaultBackgroundColor = Color.black; private Color defaultBackgroundColor = Color.black;
private Color defaultForegroundColor = Color.white; private Color defaultForegroundColor = Color.white;
UINT oldCp; UINT oldCp;
@ -860,6 +943,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
if(terminalInFamily("xterm", "rxvt", "screen")) { if(terminalInFamily("xterm", "rxvt", "screen")) {
writeStringRaw("\033[23;0t"); // restore window title from the stack writeStringRaw("\033[23;0t"); // restore window title from the stack
} }
cursor = TerminalCursor.DEFAULT;
showCursor(); showCursor();
reset(); reset();
flush(); flush();
@ -870,6 +954,10 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
version(Windows) version(Windows)
~this() { ~this() {
if(_suppressDestruction) {
flush();
return;
}
flush(); // make sure user data is all flushed before resetting flush(); // make sure user data is all flushed before resetting
reset(); reset();
showCursor(); showCursor();
@ -1061,7 +1149,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
/// Returns the terminal to normal output colors /// Returns the terminal to normal output colors
void reset() { void reset() {
version(Windows) version(Win32Console)
SetConsoleTextAttribute( SetConsoleTextAttribute(
hConsole, hConsole,
originalSbi.wAttributes); originalSbi.wAttributes);
@ -1095,7 +1183,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
executeAutoHideCursor(); executeAutoHideCursor();
version(Posix) { version(Posix) {
doTermcap("cm", y, x); doTermcap("cm", y, x);
} else version(Windows) { } else version(Win32Console) {
flush(); // if we don't do this now, the buffering can screw up the position flush(); // if we don't do this now, the buffering can screw up the position
COORD coord = {cast(short) x, cast(short) y}; COORD coord = {cast(short) x, cast(short) y};
@ -1143,7 +1231,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
private void executeAutoHideCursor() { private void executeAutoHideCursor() {
if(autoHidingCursor) { if(autoHidingCursor) {
version(Windows) version(Win32Console)
hideCursor(); hideCursor();
else version(Posix) { else version(Posix) {
// prepend the hide cursor command so it is the first thing flushed // prepend the hide cursor command so it is the first thing flushed
@ -1177,7 +1265,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
/// Changes the terminal's title /// Changes the terminal's title
void setTitle(string t) { void setTitle(string t) {
version(Windows) { version(Win32Console) {
wchar[256] buffer; wchar[256] buffer;
size_t bufferLength; size_t bufferLength;
foreach(wchar ch; t) foreach(wchar ch; t)
@ -1214,7 +1302,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
writeBuffer = writeBuffer[written .. $]; writeBuffer = writeBuffer[written .. $];
} }
} }
} else version(Windows) { } else version(Win32Console) {
import std.conv; import std.conv;
// FIXME: I'm not sure I'm actually happy with this allocation but // FIXME: I'm not sure I'm actually happy with this allocation but
// it probably isn't a big deal. At least it has unicode support now. // it probably isn't a big deal. At least it has unicode support now.
@ -1230,7 +1318,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
} }
int[] getSize() { int[] getSize() {
version(Windows) { version(Win32Console) {
CONSOLE_SCREEN_BUFFER_INFO info; CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo( hConsole, &info ); GetConsoleScreenBufferInfo( hConsole, &info );
@ -1334,7 +1422,8 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
// assert(s.indexOf("\033") == -1); // assert(s.indexOf("\033") == -1);
// tracking cursor position // tracking cursor position
foreach(ch; s) { // FIXME: by grapheme?
foreach(dchar ch; s) {
switch(ch) { switch(ch) {
case '\n': case '\n':
_cursorX = 0; _cursorX = 0;
@ -1348,8 +1437,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
_cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible _cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible
break; break;
default: default:
if(ch <= 127) // way of only advancing once per dchar instead of per code unit _cursorX++;
_cursorX++;
} }
if(_wrapAround && _cursorX > width) { if(_wrapAround && _cursorX > width) {
@ -1382,7 +1470,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
// FIXME: make sure all the data is sent, check for errors // FIXME: make sure all the data is sent, check for errors
version(Posix) { version(Posix) {
writeBuffer ~= s; // buffer it to do everything at once in flush() calls writeBuffer ~= s; // buffer it to do everything at once in flush() calls
} else version(Windows) { } else version(Win32Console) {
writeBuffer ~= s; writeBuffer ~= s;
} else static assert(0); } else static assert(0);
} }
@ -1391,7 +1479,7 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
void clear() { void clear() {
version(Posix) { version(Posix) {
doTermcap("cl"); doTermcap("cl");
} else version(Windows) { } else version(Win32Console) {
// http://support.microsoft.com/kb/99261 // http://support.microsoft.com/kb/99261
flush(); flush();
@ -1482,7 +1570,7 @@ struct RealTimeConsoleInput {
// so this hack is just to give some room for that to happen without destroying the rest of the world // so this hack is just to give some room for that to happen without destroying the rest of the world
} }
version(Windows) { version(Win32Console) {
private DWORD oldInput; private DWORD oldInput;
private DWORD oldOutput; private DWORD oldOutput;
HANDLE inputHandle; HANDLE inputHandle;
@ -1497,7 +1585,7 @@ struct RealTimeConsoleInput {
this.flags = flags; this.flags = flags;
this.terminal = terminal; this.terminal = terminal;
version(Windows) { version(Win32Console) {
inputHandle = GetStdHandle(STD_INPUT_HANDLE); inputHandle = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(inputHandle, &oldInput); GetConsoleMode(inputHandle, &oldInput);
@ -1520,11 +1608,10 @@ struct RealTimeConsoleInput {
mode = 0; mode = 0;
// we want this to match linux too // we want this to match linux too
mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */ mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */
mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */ if(!(flags & ConsoleInputFlags.noEolWrap))
mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
SetConsoleMode(terminal.hConsole, mode); SetConsoleMode(terminal.hConsole, mode);
destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); }; destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
// FIXME: change to UTF8 as well
} }
version(Posix) { version(Posix) {
@ -1615,7 +1702,7 @@ struct RealTimeConsoleInput {
version(with_eventloop) { version(with_eventloop) {
import arsd.eventloop; import arsd.eventloop;
version(Windows) version(Win32Console)
auto listenTo = inputHandle; auto listenTo = inputHandle;
else version(Posix) else version(Posix)
auto listenTo = this.fdIn; auto listenTo = this.fdIn;
@ -1654,7 +1741,7 @@ struct RealTimeConsoleInput {
void integrateWithSimpleDisplayEventLoop()(void delegate(InputEvent) userEventHandler) { void integrateWithSimpleDisplayEventLoop()(void delegate(InputEvent) userEventHandler) {
this.userEventHandler = userEventHandler; this.userEventHandler = userEventHandler;
import arsd.simpledisplay; import arsd.simpledisplay;
version(Windows) version(Win32Console)
auto listener = new WindowsHandleReader(&fdReadyReader, terminal.hConsole); auto listener = new WindowsHandleReader(&fdReadyReader, terminal.hConsole);
else version(linux) else version(linux)
auto listener = new PosixFdReader(&fdReadyReader, fdIn); auto listener = new PosixFdReader(&fdReadyReader, fdIn);
@ -1684,7 +1771,12 @@ struct RealTimeConsoleInput {
} }
} }
bool _suppressDestruction;
~this() { ~this() {
if(_suppressDestruction)
return;
// the delegate thing doesn't actually work for this... for some reason // the delegate thing doesn't actually work for this... for some reason
version(Posix) version(Posix)
if(fdIn != -1) if(fdIn != -1)
@ -1737,7 +1829,7 @@ struct RealTimeConsoleInput {
} }
bool timedCheckForInput_bypassingBuffer(int milliseconds) { bool timedCheckForInput_bypassingBuffer(int milliseconds) {
version(Windows) { version(Win32Console) {
auto response = WaitForSingleObject(terminal.hConsole, milliseconds); auto response = WaitForSingleObject(terminal.hConsole, milliseconds);
if(response == 0) if(response == 0)
return true; // the object is ready return true; // the object is ready
@ -1934,7 +2026,7 @@ struct RealTimeConsoleInput {
InputEvent[] inputQueue; InputEvent[] inputQueue;
version(Windows) version(Win32Console)
InputEvent[] readNextEvents() { InputEvent[] readNextEvents() {
terminal.flush(); // make sure all output is sent out before waiting for anything terminal.flush(); // make sure all output is sent out before waiting for anything
@ -2122,6 +2214,23 @@ struct RealTimeConsoleInput {
]; ];
} }
InputEvent[] keyPressAndRelease2(dchar c, uint modifiers = 0) {
if((flags & ConsoleInputFlags.releasedKeys))
return [
InputEvent(KeyboardEvent(true, c, modifiers), terminal),
InputEvent(KeyboardEvent(false, c, modifiers), terminal),
// old style event
InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, c, modifiers), terminal),
InputEvent(CharacterEvent(CharacterEvent.Type.Released, c, modifiers), terminal),
];
else return [
InputEvent(KeyboardEvent(true, c, modifiers), terminal),
// old style event
InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, c, modifiers), terminal)
];
}
char[30] sequenceBuffer; char[30] sequenceBuffer;
// this assumes you just read "\033[" // this assumes you just read "\033["
@ -2209,7 +2318,7 @@ struct RealTimeConsoleInput {
default: default:
// don't know it, just ignore // don't know it, just ignore
//import std.stdio; //import std.stdio;
//writeln(cap); //terminal.writeln(cap);
} }
return null; return null;
@ -2312,92 +2421,110 @@ struct RealTimeConsoleInput {
return [InputEvent(m, terminal)]; return [InputEvent(m, terminal)];
default: default:
// look it up in the termcap key database // screen doesn't actually do the modifiers, but
auto cap = terminal.findSequenceInTermcap(sequence); // it uses the same format so this branch still works fine.
if(cap !is null) { if(terminal.terminalInFamily("xterm", "screen")) {
return translateTermcapName(cap); import std.conv, std.string;
} else { auto terminator = sequence[$ - 1];
if(terminal.terminalInFamily("xterm")) { auto parts = sequence[2 .. $ - 1].split(";");
import std.conv, std.string; // parts[0] and terminator tells us the key
auto terminator = sequence[$ - 1]; // parts[1] tells us the modifierState
auto parts = sequence[2 .. $ - 1].split(";");
// parts[0] and terminator tells us the key
// parts[1] tells us the modifierState
uint modifierState; uint modifierState;
int modGot; int modGot;
if(parts.length > 1) if(parts.length > 1)
modGot = to!int(parts[1]); modGot = to!int(parts[1]);
mod_switch: switch(modGot) { mod_switch: switch(modGot) {
case 2: modifierState |= ModifierState.shift; break; case 2: modifierState |= ModifierState.shift; break;
case 3: modifierState |= ModifierState.alt; break; case 3: modifierState |= ModifierState.alt; break;
case 4: modifierState |= ModifierState.shift | ModifierState.alt; break; case 4: modifierState |= ModifierState.shift | ModifierState.alt; break;
case 5: modifierState |= ModifierState.control; break; case 5: modifierState |= ModifierState.control; break;
case 6: modifierState |= ModifierState.shift | ModifierState.control; break; case 6: modifierState |= ModifierState.shift | ModifierState.control; break;
case 7: modifierState |= ModifierState.alt | ModifierState.control; break; case 7: modifierState |= ModifierState.alt | ModifierState.control; break;
case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break; case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break;
case 9: case 9:
.. ..
case 16: case 16:
modifierState |= ModifierState.meta; modifierState |= ModifierState.meta;
if(modGot != 9) { if(modGot != 9) {
modGot -= 8; modGot -= 8;
goto mod_switch;
}
break;
// this is an extension in my own terminal emulator
case 20:
..
case 36:
modifierState |= ModifierState.windows;
modGot -= 20;
goto mod_switch; goto mod_switch;
default: }
} break;
switch(terminator) { // this is an extension in my own terminal emulator
case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState); case 20:
case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState); ..
case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState); case 36:
case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState); modifierState |= ModifierState.windows;
modGot -= 20;
goto mod_switch;
default:
}
case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState); switch(terminator) {
case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState); case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState);
case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState);
case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState);
case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState);
case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState); case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState); case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
case '~': // others case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState);
switch(parts[0]) { case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState);
case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState); case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState); case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState); case '~': // others
case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState); switch(parts[0]) {
case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState); case "1": return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState); case "4": return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState); case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState);
case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState); case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState);
case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState); case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState); case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
default:
}
break;
default: case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState);
} case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState);
} else if(terminal.terminalInFamily("rxvt")) { case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState);
// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState);
// though it isn't consistent. ugh. case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState);
} else { case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState);
// maybe we could do more terminals, but linux doesn't even send it and screen just seems to pass through, so i don't think so; xterm prolly covers most them anyway case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState);
// so this space is semi-intentionally left blank case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState);
// starting at 70 i do some magic for like shift+enter etc.
// this only happens on my own terminal emulator.
case "78": return keyPressAndRelease2('\b', modifierState);
case "79": return keyPressAndRelease2('\t', modifierState);
case "83": return keyPressAndRelease2('\n', modifierState);
default:
}
break;
default:
}
} else if(terminal.terminalInFamily("rxvt")) {
// look it up in the termcap key database
string cap = terminal.findSequenceInTermcap(sequence);
if(cap !is null) {
//terminal.writeln("found in termcap " ~ cap);
return translateTermcapName(cap);
}
// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same
// though it isn't consistent. ugh.
} else {
// maybe we could do more terminals, but linux doesn't even send it and screen just seems to pass through, so i don't think so; xterm prolly covers most them anyway
// so this space is semi-intentionally left blank
//terminal.writeln("wtf ", sequence[1..$]);
// look it up in the termcap key database
string cap = terminal.findSequenceInTermcap(sequence);
if(cap !is null) {
//terminal.writeln("found in termcap " ~ cap);
return translateTermcapName(cap);
} }
} }
} }
@ -2607,7 +2734,7 @@ struct EndOfFileEvent {}
interface CustomEvent {} interface CustomEvent {}
version(Windows) version(Win32Console)
enum ModifierState : uint { enum ModifierState : uint {
shift = 0x10, shift = 0x10,
control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl
@ -2983,13 +3110,29 @@ class LineGetter {
/// You can customize the colors here. You should set these after construction, but before /// You can customize the colors here. You should set these after construction, but before
/// calling startGettingLine or getline. /// calling startGettingLine or getline.
Color suggestionForeground; Color suggestionForeground = Color.blue;
Color regularForeground; /// . Color regularForeground = Color.DEFAULT; /// ditto
Color background; /// . Color background = Color.DEFAULT; /// ditto
Color promptColor = Color.DEFAULT; /// ditto
Color specialCharBackground = Color.green; /// ditto
//bool reverseVideo; //bool reverseVideo;
/// Set this if you want a prompt to be drawn with the line. It does NOT support color in string. /// Set this if you want a prompt to be drawn with the line. It does NOT support color in string.
string prompt; @property void prompt(string p) {
this.prompt_ = p;
promptLength = 0;
foreach(dchar c; p)
promptLength++;
}
/// ditto
@property string prompt() {
return this.prompt_;
}
private string prompt_;
private int promptLength;
/// Turn on auto suggest if you want a greyed thing of what tab /// Turn on auto suggest if you want a greyed thing of what tab
/// would be able to fill in as you type. /// would be able to fill in as you type.
@ -3087,6 +3230,8 @@ class LineGetter {
} }
} }
//private RealTimeConsoleInput* rtci;
/// One-call shop for the main workhorse /// One-call shop for the main workhorse
/// If you already have a RealTimeConsoleInput ready to go, you /// If you already have a RealTimeConsoleInput ready to go, you
/// should pass a pointer to yours here. Otherwise, LineGetter will /// should pass a pointer to yours here. Otherwise, LineGetter will
@ -3094,10 +3239,15 @@ class LineGetter {
public string getline(RealTimeConsoleInput* input = null) { public string getline(RealTimeConsoleInput* input = null) {
startGettingLine(); startGettingLine();
if(input is null) { if(input is null) {
auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents); auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents | ConsoleInputFlags.noEolWrap);
//rtci = &i;
//scope(exit) rtci = null;
while(workOnLine(i.nextEvent())) {} while(workOnLine(i.nextEvent())) {}
} else } else {
//rtci = input;
//scope(exit) rtci = null;
while(workOnLine(input.nextEvent())) {} while(workOnLine(input.nextEvent())) {}
}
return finishGettingLine(); return finishGettingLine();
} }
@ -3231,7 +3381,7 @@ class LineGetter {
} }
int availableLineLength() { int availableLineLength() {
return terminal.width - startOfLineX - cast(int) prompt.length - 1; return terminal.width - startOfLineX - promptLength - 1;
} }
private int lastDrawLength = 0; private int lastDrawLength = 0;
@ -3247,19 +3397,53 @@ class LineGetter {
if(lineLength < 0) if(lineLength < 0)
throw new Exception("too narrow terminal to draw"); throw new Exception("too narrow terminal to draw");
terminal.color(promptColor, background);
terminal.write(prompt); terminal.write(prompt);
terminal.color(regularForeground, background);
auto towrite = line[horizontalScrollPosition .. $]; auto towrite = line[horizontalScrollPosition .. $];
auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition; auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
auto cursorPositionToDrawY = 0; auto cursorPositionToDrawY = 0;
if(towrite.length > lineLength) { int written = promptLength;
towrite = towrite[0 .. lineLength];
void specialChar(char c) {
terminal.color(regularForeground, specialCharBackground);
terminal.write(c);
terminal.color(regularForeground, background);
written++;
lineLength--;
} }
terminal.write(towrite); void regularChar(dchar ch) {
import std.utf;
char[4] buffer;
auto l = encode(buffer, ch);
// note the Terminal buffers it so meh
terminal.write(buffer[0 .. l]);
lineLength -= towrite.length; written++;
lineLength--;
}
// FIXME: if there is a color at the end of the line it messes up as you scroll
// FIXME: need a way to go to multi-line editing
foreach(dchar ch; towrite) {
if(lineLength == 0)
break;
switch(ch) {
case '\n': specialChar('n'); break;
case '\r': specialChar('r'); break;
case '\a': specialChar('a'); break;
case '\t': specialChar('t'); break;
case '\b': specialChar('b'); break;
case '\033': specialChar('e'); break;
default:
regularChar(ch);
}
}
string suggestion; string suggestion;
@ -3267,20 +3451,23 @@ class LineGetter {
suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null; suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
if(suggestion.length) { if(suggestion.length) {
terminal.color(suggestionForeground, background); terminal.color(suggestionForeground, background);
terminal.write(suggestion); foreach(dchar ch; suggestion) {
if(lineLength == 0)
break;
regularChar(ch);
}
terminal.color(regularForeground, background); terminal.color(regularForeground, background);
} }
} }
// FIXME: graphemes and utf-8 on suggestion/prompt // FIXME: graphemes
auto written = cast(int) (towrite.length + suggestion.length + prompt.length);
if(written < lastDrawLength) if(written < lastDrawLength)
foreach(i; written .. lastDrawLength) foreach(i; written .. lastDrawLength)
terminal.write(" "); terminal.write(" ");
lastDrawLength = written; lastDrawLength = written;
terminal.moveTo(startOfLineX + cursorPositionToDrawX + cast(int) prompt.length, startOfLineY + cursorPositionToDrawY); terminal.moveTo(startOfLineX + cursorPositionToDrawX + promptLength, startOfLineY + cursorPositionToDrawY);
} }
/// Starts getting a new line. Call workOnLine and finishGettingLine afterward. /// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
@ -3298,10 +3485,51 @@ class LineGetter {
line.assumeSafeAppend(); line.assumeSafeAppend();
} }
updateCursorPosition(); initializeWithSize(true);
terminal.showCursor();
terminal.cursor = TerminalCursor.insert;
terminal.showCursor();
}
private void positionCursor() {
if(cursorPosition == 0)
horizontalScrollPosition = 0;
else if(cursorPosition == line.length)
scrollToEnd();
else {
// otherwise just try to center it in the screen
horizontalScrollPosition = cursorPosition;
horizontalScrollPosition -= terminal.width / 2;
// align on a code point boundary
aligned(horizontalScrollPosition, -1);
if(horizontalScrollPosition < 0)
horizontalScrollPosition = 0;
}
}
private void aligned(ref int what, int direction) {
// whereas line is right now dchar[] no need for this
// at least until we go by grapheme...
/*
while(what > 0 && what < line.length && ((line[what] & 0b1100_0000) == 0b1000_0000))
what += direction;
*/
}
private void initializeWithSize(bool firstEver = false) {
auto x = startOfLineX;
updateCursorPosition();
if(!firstEver) {
startOfLineX = x;
positionCursor();
}
lastDrawLength = terminal.width;
version(Win32Console)
lastDrawLength -= 1; // I don't like this but Windows resizing is different anyway and it is liable to scroll if i go over..
lastDrawLength = availableLineLength();
redraw(); redraw();
} }
@ -3309,7 +3537,7 @@ class LineGetter {
terminal.flush(); terminal.flush();
// then get the current cursor position to start fresh // then get the current cursor position to start fresh
version(Windows) { version(Win32Console) {
CONSOLE_SCREEN_BUFFER_INFO info; CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(terminal.hConsole, &info); GetConsoleScreenBufferInfo(terminal.hConsole, &info);
startOfLineX = info.dwCursorPosition.X; startOfLineX = info.dwCursorPosition.X;
@ -3323,6 +3551,13 @@ class LineGetter {
// We also can't use RealTimeConsoleInput here because it also does event loop stuff // We also can't use RealTimeConsoleInput here because it also does event loop stuff
// which would be broken by the child destructor :( (maybe that should be a FIXME) // which would be broken by the child destructor :( (maybe that should be a FIXME)
/+
if(rtci !is null) {
while(rtci.timedCheckForInput_bypassingBuffer(1000))
rtci.inputQueue ~= rtci.readNextEvents();
}
+/
ubyte[128] hack2; ubyte[128] hack2;
termios old; termios old;
ubyte[128] hack; ubyte[128] hack;
@ -3337,20 +3572,46 @@ class LineGetter {
terminal.writeStringRaw("\033[6n"); terminal.writeStringRaw("\033[6n");
terminal.flush(); terminal.flush();
import std.conv;
import core.stdc.errno;
import core.sys.posix.unistd; import core.sys.posix.unistd;
// reading directly to bypass any buffering // reading directly to bypass any buffering
int retries = 16;
ubyte[16] buffer; ubyte[16] buffer;
try_again:
auto len = read(terminal.fdIn, buffer.ptr, buffer.length); auto len = read(terminal.fdIn, buffer.ptr, buffer.length);
if(len <= 0) if(len <= 0) {
throw new Exception("Couldn't get cursor position to initialize get line"); if(len == -1) {
if(errno == EINTR)
goto try_again;
if(errno == EAGAIN || errno == EWOULDBLOCK) {
import core.thread;
Thread.sleep(10.msecs);
goto try_again;
}
}
throw new Exception("Couldn't get cursor position to initialize get line " ~ to!string(len) ~ " " ~ to!string(errno));
}
regot:
auto got = buffer[0 .. len]; auto got = buffer[0 .. len];
if(got.length < 6) if(got.length < 6) {
throw new Exception("not enough cursor reply answer"); auto len2 = read(terminal.fdIn, &buffer[len], buffer.length - len);
if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R') if(len2 <= 0)
throw new Exception("wrong answer for cursor position"); throw new Exception("not enough cursor reply answer");
else {
len += len2;
goto regot;
}
}
if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R') {
retries--;
if(retries > 0)
goto try_again;
throw new Exception("wrong answer for cursor position " ~ cast(string) got[1 .. $]);
}
auto gots = cast(char[]) got[2 .. $-1]; auto gots = cast(char[]) got[2 .. $-1];
import std.conv;
import std.string; import std.string;
auto pieces = split(gots, ";"); auto pieces = split(gots, ";");
@ -3366,6 +3627,14 @@ class LineGetter {
} }
private bool justHitTab; private bool justHitTab;
private bool eof;
///
string delegate(string s) pastePreprocessor;
string defaultPastePreprocessor(string s) {
return s;
}
/// for integrating into another event loop /// for integrating into another event loop
/// you can pass individual events to this and /// you can pass individual events to this and
@ -3376,6 +3645,7 @@ class LineGetter {
switch(e.type) { switch(e.type) {
case InputEvent.Type.EndOfFileEvent: case InputEvent.Type.EndOfFileEvent:
justHitTab = false; justHitTab = false;
eof = true;
// FIXME: this should be distinct from an empty line when hit at the beginning // FIXME: this should be distinct from an empty line when hit at the beginning
return false; return false;
//break; //break;
@ -3386,7 +3656,12 @@ class LineGetter {
/* Insert the character (unless it is backspace, tab, or some other control char) */ /* Insert the character (unless it is backspace, tab, or some other control char) */
auto ch = ev.which; auto ch = ev.which;
switch(ch) { switch(ch) {
version(Windows) case 26: // and this is really for Windows
goto case;
case 4: // ctrl+d will also send a newline-equivalent case 4: // ctrl+d will also send a newline-equivalent
if(line.length == 0)
eof = true;
goto case;
case '\r': case '\r':
case '\n': case '\n':
justHitTab = false; justHitTab = false;
@ -3441,9 +3716,16 @@ class LineGetter {
justHitTab = false; justHitTab = false;
if(cursorPosition) if(cursorPosition)
cursorPosition--; cursorPosition--;
if(ev.modifierState & ModifierState.control) {
while(cursorPosition && line[cursorPosition - 1] != ' ')
cursorPosition--;
}
aligned(cursorPosition, -1);
if(!multiLineMode) { if(!multiLineMode) {
if(cursorPosition < horizontalScrollPosition) if(cursorPosition < horizontalScrollPosition) {
horizontalScrollPosition--; horizontalScrollPosition--;
aligned(horizontalScrollPosition, -1);
}
} }
redraw(); redraw();
@ -3452,9 +3734,20 @@ class LineGetter {
justHitTab = false; justHitTab = false;
if(cursorPosition < line.length) if(cursorPosition < line.length)
cursorPosition++; cursorPosition++;
if(ev.modifierState & ModifierState.control) {
while(cursorPosition + 1 < line.length && line[cursorPosition + 1] != ' ')
cursorPosition++;
cursorPosition += 2;
if(cursorPosition > line.length)
cursorPosition = cast(int) line.length;
}
aligned(cursorPosition, 1);
if(!multiLineMode) { if(!multiLineMode) {
if(cursorPosition >= horizontalScrollPosition + availableLineLength()) if(cursorPosition >= horizontalScrollPosition + availableLineLength()) {
horizontalScrollPosition++; horizontalScrollPosition++;
aligned(horizontalScrollPosition, 1);
}
} }
redraw(); redraw();
@ -3495,9 +3788,24 @@ class LineGetter {
break; break;
case KeyboardEvent.Key.Insert: case KeyboardEvent.Key.Insert:
justHitTab = false; justHitTab = false;
insertMode = !insertMode; if(ev.modifierState & ModifierState.shift) {
// FIXME: indicate this on the UI somehow // paste
// like change the cursor or something
// shift+insert = request paste
// ctrl+insert = request copy. but that needs a selection
// those work on Windows!!!! and many linux TEs too.
// but if it does make it here, we'll attempt it at this level
} else if(ev.modifierState & ModifierState.control) {
// copy
} else {
insertMode = !insertMode;
if(insertMode)
terminal.cursor = TerminalCursor.insert;
else
terminal.cursor = TerminalCursor.block;
}
break; break;
case KeyboardEvent.Key.Delete: case KeyboardEvent.Key.Delete:
justHitTab = false; justHitTab = false;
@ -3521,7 +3829,10 @@ class LineGetter {
break; break;
case InputEvent.Type.PasteEvent: case InputEvent.Type.PasteEvent:
justHitTab = false; justHitTab = false;
addString(e.pasteEvent.pastedText); if(pastePreprocessor)
addString(pastePreprocessor(e.pasteEvent.pastedText));
else
addString(defaultPastePreprocessor(e.pasteEvent.pastedText));
redraw(); redraw();
break; break;
case InputEvent.Type.MouseEvent: case InputEvent.Type.MouseEvent:
@ -3532,8 +3843,7 @@ class LineGetter {
if(me.eventType == MouseEvent.Type.Pressed) { if(me.eventType == MouseEvent.Type.Pressed) {
if(me.buttons & MouseEvent.Button.Left) { if(me.buttons & MouseEvent.Button.Left) {
if(me.y == startOfLineY) { if(me.y == startOfLineY) {
// FIXME: prompt.length should be graphemes or at least code poitns int p = me.x - startOfLineX - promptLength + horizontalScrollPosition;
int p = me.x - startOfLineX - cast(int) prompt.length + horizontalScrollPosition;
if(p >= 0 && p < line.length) { if(p >= 0 && p < line.length) {
justHitTab = false; justHitTab = false;
cursorPosition = p; cursorPosition = p;
@ -3547,6 +3857,7 @@ class LineGetter {
/* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent /* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent
yourself and then don't pass it to this function. */ yourself and then don't pass it to this function. */
// FIXME // FIXME
initializeWithSize();
break; break;
case InputEvent.Type.UserInterruptionEvent: case InputEvent.Type.UserInterruptionEvent:
/* I'll take this as canceling the line. */ /* I'll take this as canceling the line. */
@ -3571,7 +3882,7 @@ class LineGetter {
this.history ~= history; this.history ~= history;
// FIXME: we should hide the cursor if it was hidden in the call to startGettingLine // FIXME: we should hide the cursor if it was hidden in the call to startGettingLine
return f; return eof ? null : f.length ? f : "";
} }
} }