libluna+kernel: Basic ANSI escape sequences
This commit is contained in:
parent
e76a91d5d0
commit
c524dc8d58
@ -3,6 +3,7 @@
|
|||||||
#include "video/Framebuffer.h"
|
#include "video/Framebuffer.h"
|
||||||
#include <luna/CString.h>
|
#include <luna/CString.h>
|
||||||
#include <luna/CType.h>
|
#include <luna/CType.h>
|
||||||
|
#include <luna/EscapeSequence.h>
|
||||||
#include <luna/Format.h>
|
#include <luna/Format.h>
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
#include <luna/ScopeGuard.h>
|
#include <luna/ScopeGuard.h>
|
||||||
@ -13,20 +14,43 @@ extern const BOOTBOOT bootboot;
|
|||||||
|
|
||||||
#include "video/BuiltinFont.h"
|
#include "video/BuiltinFont.h"
|
||||||
|
|
||||||
static constexpr u32 BLACK = 0xff000000;
|
// Default text color.
|
||||||
static constexpr u32 WHITE = 0xffffffff;
|
static constexpr u32 WHITE = 0xffffffff;
|
||||||
|
|
||||||
|
// xterm color palette.
|
||||||
|
static constexpr u32 BLACK = 0xff000000;
|
||||||
|
static constexpr u32 RED = 0xffcd0000;
|
||||||
|
static constexpr u32 GREEN = 0xff00cd00;
|
||||||
|
static constexpr u32 YELLOW = 0xffcdcd00;
|
||||||
|
static constexpr u32 BLUE = 0xff0000ee;
|
||||||
|
static constexpr u32 MAGENTA = 0xffcd00cd;
|
||||||
|
static constexpr u32 CYAN = 0xff00cdcd;
|
||||||
|
static constexpr u32 GRAY = 0xffe5e5e5;
|
||||||
|
|
||||||
|
static constexpr u32 BRIGHT_BLACK = 0xff7f7f7f;
|
||||||
|
static constexpr u32 BRIGHT_RED = 0xffff0000;
|
||||||
|
static constexpr u32 BRIGHT_GREEN = 0xff00ff00;
|
||||||
|
static constexpr u32 BRIGHT_YELLOW = 0xffffff00;
|
||||||
|
static constexpr u32 BRIGHT_BLUE = 0xff5c5cff;
|
||||||
|
static constexpr u32 BRIGHT_MAGENTA = 0xffff00ff;
|
||||||
|
static constexpr u32 BRIGHT_CYAN = 0xff00ffff;
|
||||||
|
static constexpr u32 BRIGHT_GRAY = 0xffffffff;
|
||||||
|
|
||||||
static u32 g_background_color = BLACK;
|
static u32 g_background_color = BLACK;
|
||||||
static u32 g_foreground_color = WHITE;
|
static u32 g_foreground_color = WHITE;
|
||||||
|
|
||||||
static constexpr u32 FONT_HEIGHT = 16;
|
static constexpr u32 FONT_HEIGHT = 16;
|
||||||
static constexpr u32 FONT_WIDTH = 8;
|
static constexpr u32 FONT_WIDTH = 8;
|
||||||
|
|
||||||
|
static bool bold = false;
|
||||||
|
|
||||||
static u32 g_x_position = 0;
|
static u32 g_x_position = 0;
|
||||||
static u32 g_y_position = 0;
|
static u32 g_y_position = 0;
|
||||||
|
|
||||||
static Utf8StateDecoder utf8_decoder;
|
static Utf8StateDecoder utf8_decoder;
|
||||||
|
|
||||||
|
static Option<EscapeSequenceParser> escape_sequence_parser;
|
||||||
|
|
||||||
static void putwchar_at(wchar_t c, u32 x, u32 y)
|
static void putwchar_at(wchar_t c, u32 x, u32 y)
|
||||||
{
|
{
|
||||||
const u8* glyph = &font[c * 16];
|
const u8* glyph = &font[c * 16];
|
||||||
@ -87,6 +111,206 @@ static bool at_end_of_screen()
|
|||||||
return (g_x_position + FONT_WIDTH) > Framebuffer::width();
|
return (g_x_position + FONT_WIDTH) > Framebuffer::width();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool handle_escape_sequence(wchar_t c)
|
||||||
|
{
|
||||||
|
auto rc = escape_sequence_parser->advance(static_cast<u8>(c));
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!rc.value()) return true;
|
||||||
|
if (!escape_sequence_parser->valid())
|
||||||
|
{
|
||||||
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& params = escape_sequence_parser->parameters();
|
||||||
|
switch (escape_sequence_parser->code())
|
||||||
|
{
|
||||||
|
case EscapeCode::CursorUp: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * FONT_HEIGHT;
|
||||||
|
if ((u32)pixels > g_y_position) g_y_position = 0;
|
||||||
|
else
|
||||||
|
g_y_position -= pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorDown: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * FONT_HEIGHT;
|
||||||
|
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
|
||||||
|
else
|
||||||
|
g_y_position += pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorBack: {
|
||||||
|
int chars = params.size() ? params[0] : 1;
|
||||||
|
int pixels = chars * FONT_WIDTH;
|
||||||
|
if ((u32)pixels > g_x_position) g_x_position = 0;
|
||||||
|
else
|
||||||
|
g_x_position -= pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorForward: {
|
||||||
|
int chars = params.size() ? params[0] : 1;
|
||||||
|
int pixels = chars * FONT_WIDTH;
|
||||||
|
if (pixels + g_x_position >= Framebuffer::width()) g_x_position = Framebuffer::width() - FONT_WIDTH;
|
||||||
|
else
|
||||||
|
g_x_position += pixels;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorNextLine: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * FONT_HEIGHT;
|
||||||
|
if ((u32)pixels > g_y_position) g_y_position = 0;
|
||||||
|
else
|
||||||
|
g_y_position -= pixels;
|
||||||
|
g_x_position = 0;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorPreviousLine: {
|
||||||
|
int lines = params.size() ? params[0] : 1;
|
||||||
|
int pixels = lines * FONT_HEIGHT;
|
||||||
|
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
|
||||||
|
else
|
||||||
|
g_y_position += pixels;
|
||||||
|
g_x_position = 0;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::CursorHorizontalAbsolute: {
|
||||||
|
int line = (params.size() ? params[0] : 1) - 1;
|
||||||
|
if (line < 0) break;
|
||||||
|
u32 position = line * FONT_HEIGHT;
|
||||||
|
if (position >= Framebuffer::height()) position = Framebuffer::height() - FONT_HEIGHT;
|
||||||
|
g_y_position = position;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::SetCursorPosition: {
|
||||||
|
int x = (params.size() ? params[0] : 1) - 1;
|
||||||
|
int y = (params.size() > 1 ? params[1] : 1) - 1;
|
||||||
|
if (x < 0 || y < 0) break;
|
||||||
|
u32 x_position = x * FONT_WIDTH;
|
||||||
|
if (x_position >= Framebuffer::width()) x_position = Framebuffer::width() - FONT_HEIGHT;
|
||||||
|
g_x_position = x_position;
|
||||||
|
u32 y_position = y * FONT_HEIGHT;
|
||||||
|
if (y_position >= Framebuffer::height()) y_position = Framebuffer::height() - FONT_HEIGHT;
|
||||||
|
g_y_position = y_position;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case EscapeCode::SelectGraphicRendition: {
|
||||||
|
if (!params.size())
|
||||||
|
{
|
||||||
|
g_foreground_color = WHITE;
|
||||||
|
g_background_color = BLACK;
|
||||||
|
bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (usize i = 0; i < params.size(); i++)
|
||||||
|
{
|
||||||
|
int arg = params[i];
|
||||||
|
switch (arg)
|
||||||
|
{
|
||||||
|
case 0: {
|
||||||
|
g_foreground_color = BLACK;
|
||||||
|
g_background_color = WHITE;
|
||||||
|
bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
bold = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 22: {
|
||||||
|
bold = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 30: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_BLACK : BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 31: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_RED : RED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 32: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_GREEN : GREEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 33: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_YELLOW : YELLOW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 34: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_BLUE : BLUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 35: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_MAGENTA : MAGENTA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 36: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_CYAN : CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 37: {
|
||||||
|
g_foreground_color = bold ? BRIGHT_GRAY : GRAY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 39: {
|
||||||
|
g_foreground_color = WHITE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 40: {
|
||||||
|
g_background_color = bold ? BRIGHT_BLACK : BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 41: {
|
||||||
|
g_background_color = bold ? BRIGHT_RED : RED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 42: {
|
||||||
|
g_background_color = bold ? BRIGHT_GREEN : GREEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 43: {
|
||||||
|
g_background_color = bold ? BRIGHT_YELLOW : YELLOW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 44: {
|
||||||
|
g_background_color = bold ? BRIGHT_BLUE : BLUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 45: {
|
||||||
|
g_background_color = bold ? BRIGHT_MAGENTA : MAGENTA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 46: {
|
||||||
|
g_background_color = bold ? BRIGHT_CYAN : CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 47: {
|
||||||
|
g_background_color = bold ? BRIGHT_GRAY : GRAY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 49: {
|
||||||
|
g_background_color = BLACK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace TextConsole
|
namespace TextConsole
|
||||||
{
|
{
|
||||||
void putwchar(wchar_t c)
|
void putwchar(wchar_t c)
|
||||||
@ -94,6 +318,11 @@ namespace TextConsole
|
|||||||
// Unprintable (not in the built-in font) characters get represented as a box
|
// Unprintable (not in the built-in font) characters get represented as a box
|
||||||
if (c > (wchar_t)255) c = (wchar_t)256;
|
if (c > (wchar_t)255) c = (wchar_t)256;
|
||||||
|
|
||||||
|
if (escape_sequence_parser.has_value())
|
||||||
|
{
|
||||||
|
if (handle_escape_sequence(c)) return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
case L'\n': {
|
case L'\n': {
|
||||||
@ -113,6 +342,10 @@ namespace TextConsole
|
|||||||
erase_current_char();
|
erase_current_char();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case L'\x1b':
|
||||||
|
case L'\x9b':
|
||||||
|
case L'\x90':
|
||||||
|
case L'\x9d': escape_sequence_parser = EscapeSequenceParser { (u8)c }; break;
|
||||||
default: {
|
default: {
|
||||||
if (_iscntrl(c)) return;
|
if (_iscntrl(c)) return;
|
||||||
putwchar_at(c, g_x_position, g_y_position);
|
putwchar_at(c, g_x_position, g_y_position);
|
||||||
|
@ -5,6 +5,7 @@ file(GLOB HEADERS include/luna/*.h)
|
|||||||
set(FREESTANDING_SOURCES
|
set(FREESTANDING_SOURCES
|
||||||
${HEADERS}
|
${HEADERS}
|
||||||
src/CRC32.cpp
|
src/CRC32.cpp
|
||||||
|
src/EscapeSequence.cpp
|
||||||
src/Format.cpp
|
src/Format.cpp
|
||||||
src/Sort.cpp
|
src/Sort.cpp
|
||||||
src/NumberParsing.cpp
|
src/NumberParsing.cpp
|
||||||
|
67
libluna/include/luna/EscapeSequence.h
Normal file
67
libluna/include/luna/EscapeSequence.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* @file EscapeSequence.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief ANSI escape sequence parsing.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <luna/Vector.h>
|
||||||
|
|
||||||
|
enum class EscapeCode
|
||||||
|
{
|
||||||
|
SaveCursor,
|
||||||
|
RestoreCursor,
|
||||||
|
CursorUp,
|
||||||
|
CursorDown,
|
||||||
|
CursorForward,
|
||||||
|
CursorBack,
|
||||||
|
CursorNextLine,
|
||||||
|
CursorPreviousLine,
|
||||||
|
CursorHorizontalAbsolute,
|
||||||
|
SetCursorPosition,
|
||||||
|
SelectGraphicRendition,
|
||||||
|
};
|
||||||
|
|
||||||
|
class EscapeSequenceParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EscapeSequenceParser(u8 begin);
|
||||||
|
|
||||||
|
Result<bool> advance(u8 byte);
|
||||||
|
|
||||||
|
bool valid() const
|
||||||
|
{
|
||||||
|
return m_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vector<int>& parameters() const
|
||||||
|
{
|
||||||
|
return m_parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
EscapeCode code() const
|
||||||
|
{
|
||||||
|
return m_escape_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class SequenceType
|
||||||
|
{
|
||||||
|
ESC,
|
||||||
|
CSI,
|
||||||
|
DCS,
|
||||||
|
OSC,
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<u8> m_parameter;
|
||||||
|
Vector<int> m_parameters;
|
||||||
|
|
||||||
|
SequenceType m_sequence_type;
|
||||||
|
bool m_parsing_parameter { false };
|
||||||
|
bool m_valid { false };
|
||||||
|
|
||||||
|
EscapeCode m_escape_code;
|
||||||
|
};
|
146
libluna/src/EscapeSequence.cpp
Normal file
146
libluna/src/EscapeSequence.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* @file EscapeSequence.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief ANSI escape sequence parsing.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <luna/CType.h>
|
||||||
|
#include <luna/Check.h>
|
||||||
|
#include <luna/EscapeSequence.h>
|
||||||
|
#include <luna/NumberParsing.h>
|
||||||
|
|
||||||
|
EscapeSequenceParser::EscapeSequenceParser(u8 begin)
|
||||||
|
{
|
||||||
|
switch (begin)
|
||||||
|
{
|
||||||
|
case 0x1b: m_sequence_type = SequenceType::ESC; break;
|
||||||
|
case 0x9b: m_sequence_type = SequenceType::CSI; break;
|
||||||
|
case 0x90: m_sequence_type = SequenceType::DCS; break;
|
||||||
|
case 0x9d: m_sequence_type = SequenceType::OSC; break;
|
||||||
|
default: fail("Unrecognized escape sequence type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> EscapeSequenceParser::advance(u8 byte)
|
||||||
|
{
|
||||||
|
switch (m_sequence_type)
|
||||||
|
{
|
||||||
|
case SequenceType::ESC: {
|
||||||
|
switch (byte)
|
||||||
|
{
|
||||||
|
case '[': {
|
||||||
|
m_sequence_type = SequenceType::CSI;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
case 'P': {
|
||||||
|
m_sequence_type = SequenceType::DCS;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
case ']': {
|
||||||
|
m_sequence_type = SequenceType::OSC;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
case '7': {
|
||||||
|
m_escape_code = EscapeCode::SaveCursor;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case '8': {
|
||||||
|
m_escape_code = EscapeCode::RestoreCursor;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
default: {
|
||||||
|
m_valid = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case SequenceType::CSI: {
|
||||||
|
if (_isdigit(byte))
|
||||||
|
{
|
||||||
|
m_parsing_parameter = true;
|
||||||
|
TRY(m_parameter.try_append(byte));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_parsing_parameter && byte == ';')
|
||||||
|
{
|
||||||
|
TRY(m_parameters.try_append(0));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_parsing_parameter)
|
||||||
|
{
|
||||||
|
TRY(m_parameter.try_append(0));
|
||||||
|
int value = static_cast<int>(parse_unsigned_integer((const char*)m_parameter.data(), nullptr, 10));
|
||||||
|
m_parameter.clear();
|
||||||
|
TRY(m_parameters.try_append(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (byte)
|
||||||
|
{
|
||||||
|
case 'A': {
|
||||||
|
m_escape_code = EscapeCode::CursorUp;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'B': {
|
||||||
|
m_escape_code = EscapeCode::CursorDown;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'C': {
|
||||||
|
m_escape_code = EscapeCode::CursorForward;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'D': {
|
||||||
|
m_escape_code = EscapeCode::CursorBack;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'E': {
|
||||||
|
m_escape_code = EscapeCode::CursorNextLine;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'F': {
|
||||||
|
m_escape_code = EscapeCode::CursorPreviousLine;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'G': {
|
||||||
|
m_escape_code = EscapeCode::CursorHorizontalAbsolute;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'H': {
|
||||||
|
m_escape_code = EscapeCode::SetCursorPosition;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case 'm': {
|
||||||
|
m_escape_code = EscapeCode::SelectGraphicRendition;
|
||||||
|
m_valid = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case ';': {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
default: {
|
||||||
|
m_valid = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case SequenceType::DCS: todo();
|
||||||
|
case SequenceType::OSC: todo();
|
||||||
|
default: todo();
|
||||||
|
}
|
||||||
|
}
|
@ -133,7 +133,8 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
if (interactive)
|
if (interactive)
|
||||||
{
|
{
|
||||||
auto cwd = TRY(os::FileSystem::working_directory());
|
auto cwd = TRY(os::FileSystem::working_directory());
|
||||||
os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end);
|
os::print("\x1b[%dm%s\x1b[m@\x1b[36m%s\x1b[m:\x1b[1;34m%s\x1b[m%c ", getuid() == 0 ? 31 : 35, username,
|
||||||
|
hostname, cwd.chars(), prompt_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto maybe_cmd = input_file->read_line();
|
auto maybe_cmd = input_file->read_line();
|
||||||
|
Loading…
Reference in New Issue
Block a user