mirror of https://github.com/adamdruppe/arsd.git
merge
This commit is contained in:
commit
bd7a3fcc32
69
http2.d
69
http2.d
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
591
terminal.d
591
terminal.d
|
@ -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 : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue