Replace ncurses with a naive custom terminal backend

This commit is contained in:
Maxime Coste 2019-11-13 23:31:24 +11:00
parent 911edf6ea3
commit c12699d2e9
3 changed files with 141 additions and 326 deletions

View File

@ -1000,7 +1000,7 @@ int run_pipe(StringView session)
void signal_handler(int signal) void signal_handler(int signal)
{ {
NCursesUI::abort(); NCursesUI::restore_terminal();
const char* text = nullptr; const char* text = nullptr;
switch (signal) switch (signal)
{ {

View File

@ -10,11 +10,6 @@
#include <algorithm> #include <algorithm>
#define NCURSES_OPAQUE 0
#define NCURSES_INTERNALS
#include <ncurses.h>
#include <fcntl.h> #include <fcntl.h>
#include <csignal> #include <csignal>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -29,94 +24,110 @@ namespace Kakoune
using std::min; using std::min;
using std::max; using std::max;
struct NCursesWin : WINDOW {}; static void set_cursor_pos(DisplayCoord coord)
{
printf("\033[%d;%dH", (int)coord.line + 1, (int)coord.column + 1);
}
void NCursesUI::Window::create(const DisplayCoord& p, const DisplayCoord& s) void NCursesUI::Window::create(const DisplayCoord& p, const DisplayCoord& s)
{ {
pos = p; pos = p;
size = s; size = s;
win = (NCursesWin*)newpad((int)size.line, (int)size.column); lines.resize((int)size.line);
} }
void NCursesUI::Window::destroy() void NCursesUI::Window::destroy()
{ {
delwin(win);
win = nullptr;
pos = DisplayCoord{}; pos = DisplayCoord{};
size = DisplayCoord{}; size = DisplayCoord{};
lines.clear();
} }
void NCursesUI::Window::refresh(bool force) void NCursesUI::Window::refresh(bool force)
{ {
if (not win) if (lines.empty())
return; return;
if (force) static constexpr int fg_table[]{ 39, 30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97 };
redrawwin(win); static constexpr int bg_table[]{ 49, 40, 41, 42, 43, 44, 45, 46, 47, 100, 101, 102, 103, 104, 105, 106, 107 };
static constexpr int attr_table[]{ 0, 4, 7, 5, 1, 2, 3 };
auto set_color = [](bool fg, const Color& color) {
if (color.isRGB())
printf(";%d;2;%d;%d;%d", fg ? 38 : 48, color.r, color.g, color.b);
else
printf(";%d", (fg ? fg_table : bg_table)[(int)(char)color.color]);
};
DisplayCoord max_pos = pos + size - DisplayCoord{1,1}; auto set_attributes = [](const Attribute& attributes) {
pnoutrefresh(win, 0, 0, (int)pos.line, (int)pos.column, for (int i = 0; i < sizeof(attr_table) / sizeof(int); ++i)
(int)max_pos.line, (int)max_pos.column); {
if (attributes & (Attribute)(1 << i))
printf(";%d", attr_table[i]);
}
};
DisplayCoord cursor_pos = pos;
for (auto& line : lines)
{
set_cursor_pos(cursor_pos);
for (auto& atom : line)
{
printf("\033[");
set_attributes(atom.face.attributes);
set_color(true, atom.face.fg);
set_color(false, atom.face.bg);
printf("m");
fputs(atom.text.c_str(), stdout);
}
++cursor_pos.line;
}
} }
void NCursesUI::Window::move_cursor(DisplayCoord coord) void NCursesUI::Window::move_cursor(DisplayCoord coord)
{ {
wmove(win, (int)coord.line, (int)coord.column); cursor = coord;
} }
void NCursesUI::Window::draw(Palette& palette, ConstArrayView<DisplayAtom> atoms, void NCursesUI::Window::clear_line()
{
auto& line = lines[(int)cursor.line];
auto it = line.begin();
ColumnCount column = 0;
for (; it != line.end() and column < cursor.column; ++it)
column += it->text.column_length();
line.erase(it, line.end());
if (column > cursor.column)
{
auto& text = line.back().text;
auto new_length = text.column_length() - (column - cursor.column);
text.resize(text.byte_count_to(new_length), 0);
}
}
void NCursesUI::Window::draw(ConstArrayView<DisplayAtom> atoms,
const Face& default_face) const Face& default_face)
{ {
auto add_str = [&](StringView str) { waddnstr(win, str.begin(), (int)str.length()); }; clear_line();
auto set_face = [&](Face face) {
if (m_active_pair != -1)
wattroff(win, COLOR_PAIR(m_active_pair));
face = merge_faces(default_face, face);
if (face.fg != Color::Default or face.bg != Color::Default)
{
m_active_pair = palette.get_color_pair(face);
wattron(win, COLOR_PAIR(m_active_pair));
}
auto set_attribute = [&](Attribute attr, int nc_attr) {
(face.attributes & attr) ? wattron(win, nc_attr) : wattroff(win, nc_attr);
};
set_attribute(Attribute::Underline, A_UNDERLINE);
set_attribute(Attribute::Reverse, A_REVERSE);
set_attribute(Attribute::Blink, A_BLINK);
set_attribute(Attribute::Bold, A_BOLD);
set_attribute(Attribute::Dim, A_DIM);
#if defined(A_ITALIC)
set_attribute(Attribute::Italic, A_ITALIC);
#endif
};
wbkgdset(win, COLOR_PAIR(palette.get_color_pair(default_face)));
ColumnCount column = getcurx(win);
for (const DisplayAtom& atom : atoms) for (const DisplayAtom& atom : atoms)
{ {
StringView content = atom.content(); StringView content = atom.content();
if (content.empty()) if (content.empty())
continue; continue;
set_face(atom.face); auto face = merge_faces(default_face, atom.face);
if (content.back() == '\n') if (content.back() == '\n')
{ {
add_str(content.substr(0, content.length()-1)); lines[(int)cursor.line].push_back({content.substr(0, content.length()-1).str(), face});
waddch(win, ' '); lines[(int)cursor.line].push_back({" ", face});
} }
else else
add_str(content); lines[(int)cursor.line].push_back({content.str(), face});
column += content.column_length(); cursor.column += content.column_length();
} }
if (column < size.column) if (cursor.column < size.column)
wclrtoeol(win); lines[(int)cursor.line].push_back({String(' ', size.column - cursor.column), default_face});
} }
constexpr int NCursesUI::default_shift_function_key; constexpr int NCursesUI::default_shift_function_key;
@ -158,163 +169,6 @@ static constexpr StringView assistant_dilbert[] =
template<typename T> T sq(T x) { return x * x; } template<typename T> T sq(T x) { return x * x; }
constexpr struct { unsigned char r, g, b; } builtin_colors[] = {
{0x00,0x00,0x00}, {0x80,0x00,0x00}, {0x00,0x80,0x00}, {0x80,0x80,0x00},
{0x00,0x00,0x80}, {0x80,0x00,0x80}, {0x00,0x80,0x80}, {0xc0,0xc0,0xc0},
{0x80,0x80,0x80}, {0xff,0x00,0x00}, {0x00,0xff,0x00}, {0xff,0xff,0x00},
{0x00,0x00,0xff}, {0xff,0x00,0xff}, {0x00,0xff,0xff}, {0xff,0xff,0xff},
{0x00,0x00,0x00}, {0x00,0x00,0x5f}, {0x00,0x00,0x87}, {0x00,0x00,0xaf},
{0x00,0x00,0xd7}, {0x00,0x00,0xff}, {0x00,0x5f,0x00}, {0x00,0x5f,0x5f},
{0x00,0x5f,0x87}, {0x00,0x5f,0xaf}, {0x00,0x5f,0xd7}, {0x00,0x5f,0xff},
{0x00,0x87,0x00}, {0x00,0x87,0x5f}, {0x00,0x87,0x87}, {0x00,0x87,0xaf},
{0x00,0x87,0xd7}, {0x00,0x87,0xff}, {0x00,0xaf,0x00}, {0x00,0xaf,0x5f},
{0x00,0xaf,0x87}, {0x00,0xaf,0xaf}, {0x00,0xaf,0xd7}, {0x00,0xaf,0xff},
{0x00,0xd7,0x00}, {0x00,0xd7,0x5f}, {0x00,0xd7,0x87}, {0x00,0xd7,0xaf},
{0x00,0xd7,0xd7}, {0x00,0xd7,0xff}, {0x00,0xff,0x00}, {0x00,0xff,0x5f},
{0x00,0xff,0x87}, {0x00,0xff,0xaf}, {0x00,0xff,0xd7}, {0x00,0xff,0xff},
{0x5f,0x00,0x00}, {0x5f,0x00,0x5f}, {0x5f,0x00,0x87}, {0x5f,0x00,0xaf},
{0x5f,0x00,0xd7}, {0x5f,0x00,0xff}, {0x5f,0x5f,0x00}, {0x5f,0x5f,0x5f},
{0x5f,0x5f,0x87}, {0x5f,0x5f,0xaf}, {0x5f,0x5f,0xd7}, {0x5f,0x5f,0xff},
{0x5f,0x87,0x00}, {0x5f,0x87,0x5f}, {0x5f,0x87,0x87}, {0x5f,0x87,0xaf},
{0x5f,0x87,0xd7}, {0x5f,0x87,0xff}, {0x5f,0xaf,0x00}, {0x5f,0xaf,0x5f},
{0x5f,0xaf,0x87}, {0x5f,0xaf,0xaf}, {0x5f,0xaf,0xd7}, {0x5f,0xaf,0xff},
{0x5f,0xd7,0x00}, {0x5f,0xd7,0x5f}, {0x5f,0xd7,0x87}, {0x5f,0xd7,0xaf},
{0x5f,0xd7,0xd7}, {0x5f,0xd7,0xff}, {0x5f,0xff,0x00}, {0x5f,0xff,0x5f},
{0x5f,0xff,0x87}, {0x5f,0xff,0xaf}, {0x5f,0xff,0xd7}, {0x5f,0xff,0xff},
{0x87,0x00,0x00}, {0x87,0x00,0x5f}, {0x87,0x00,0x87}, {0x87,0x00,0xaf},
{0x87,0x00,0xd7}, {0x87,0x00,0xff}, {0x87,0x5f,0x00}, {0x87,0x5f,0x5f},
{0x87,0x5f,0x87}, {0x87,0x5f,0xaf}, {0x87,0x5f,0xd7}, {0x87,0x5f,0xff},
{0x87,0x87,0x00}, {0x87,0x87,0x5f}, {0x87,0x87,0x87}, {0x87,0x87,0xaf},
{0x87,0x87,0xd7}, {0x87,0x87,0xff}, {0x87,0xaf,0x00}, {0x87,0xaf,0x5f},
{0x87,0xaf,0x87}, {0x87,0xaf,0xaf}, {0x87,0xaf,0xd7}, {0x87,0xaf,0xff},
{0x87,0xd7,0x00}, {0x87,0xd7,0x5f}, {0x87,0xd7,0x87}, {0x87,0xd7,0xaf},
{0x87,0xd7,0xd7}, {0x87,0xd7,0xff}, {0x87,0xff,0x00}, {0x87,0xff,0x5f},
{0x87,0xff,0x87}, {0x87,0xff,0xaf}, {0x87,0xff,0xd7}, {0x87,0xff,0xff},
{0xaf,0x00,0x00}, {0xaf,0x00,0x5f}, {0xaf,0x00,0x87}, {0xaf,0x00,0xaf},
{0xaf,0x00,0xd7}, {0xaf,0x00,0xff}, {0xaf,0x5f,0x00}, {0xaf,0x5f,0x5f},
{0xaf,0x5f,0x87}, {0xaf,0x5f,0xaf}, {0xaf,0x5f,0xd7}, {0xaf,0x5f,0xff},
{0xaf,0x87,0x00}, {0xaf,0x87,0x5f}, {0xaf,0x87,0x87}, {0xaf,0x87,0xaf},
{0xaf,0x87,0xd7}, {0xaf,0x87,0xff}, {0xaf,0xaf,0x00}, {0xaf,0xaf,0x5f},
{0xaf,0xaf,0x87}, {0xaf,0xaf,0xaf}, {0xaf,0xaf,0xd7}, {0xaf,0xaf,0xff},
{0xaf,0xd7,0x00}, {0xaf,0xd7,0x5f}, {0xaf,0xd7,0x87}, {0xaf,0xd7,0xaf},
{0xaf,0xd7,0xd7}, {0xaf,0xd7,0xff}, {0xaf,0xff,0x00}, {0xaf,0xff,0x5f},
{0xaf,0xff,0x87}, {0xaf,0xff,0xaf}, {0xaf,0xff,0xd7}, {0xaf,0xff,0xff},
{0xd7,0x00,0x00}, {0xd7,0x00,0x5f}, {0xd7,0x00,0x87}, {0xd7,0x00,0xaf},
{0xd7,0x00,0xd7}, {0xd7,0x00,0xff}, {0xd7,0x5f,0x00}, {0xd7,0x5f,0x5f},
{0xd7,0x5f,0x87}, {0xd7,0x5f,0xaf}, {0xd7,0x5f,0xd7}, {0xd7,0x5f,0xff},
{0xd7,0x87,0x00}, {0xd7,0x87,0x5f}, {0xd7,0x87,0x87}, {0xd7,0x87,0xaf},
{0xd7,0x87,0xd7}, {0xd7,0x87,0xff}, {0xd7,0xaf,0x00}, {0xd7,0xaf,0x5f},
{0xd7,0xaf,0x87}, {0xd7,0xaf,0xaf}, {0xd7,0xaf,0xd7}, {0xd7,0xaf,0xff},
{0xd7,0xd7,0x00}, {0xd7,0xd7,0x5f}, {0xd7,0xd7,0x87}, {0xd7,0xd7,0xaf},
{0xd7,0xd7,0xd7}, {0xd7,0xd7,0xff}, {0xd7,0xff,0x00}, {0xd7,0xff,0x5f},
{0xd7,0xff,0x87}, {0xd7,0xff,0xaf}, {0xd7,0xff,0xd7}, {0xd7,0xff,0xff},
{0xff,0x00,0x00}, {0xff,0x00,0x5f}, {0xff,0x00,0x87}, {0xff,0x00,0xaf},
{0xff,0x00,0xd7}, {0xff,0x00,0xff}, {0xff,0x5f,0x00}, {0xff,0x5f,0x5f},
{0xff,0x5f,0x87}, {0xff,0x5f,0xaf}, {0xff,0x5f,0xd7}, {0xff,0x5f,0xff},
{0xff,0x87,0x00}, {0xff,0x87,0x5f}, {0xff,0x87,0x87}, {0xff,0x87,0xaf},
{0xff,0x87,0xd7}, {0xff,0x87,0xff}, {0xff,0xaf,0x00}, {0xff,0xaf,0x5f},
{0xff,0xaf,0x87}, {0xff,0xaf,0xaf}, {0xff,0xaf,0xd7}, {0xff,0xaf,0xff},
{0xff,0xd7,0x00}, {0xff,0xd7,0x5f}, {0xff,0xd7,0x87}, {0xff,0xd7,0xaf},
{0xff,0xd7,0xd7}, {0xff,0xd7,0xff}, {0xff,0xff,0x00}, {0xff,0xff,0x5f},
{0xff,0xff,0x87}, {0xff,0xff,0xaf}, {0xff,0xff,0xd7}, {0xff,0xff,0xff},
{0x08,0x08,0x08}, {0x12,0x12,0x12}, {0x1c,0x1c,0x1c}, {0x26,0x26,0x26},
{0x30,0x30,0x30}, {0x3a,0x3a,0x3a}, {0x44,0x44,0x44}, {0x4e,0x4e,0x4e},
{0x58,0x58,0x58}, {0x60,0x60,0x60}, {0x66,0x66,0x66}, {0x76,0x76,0x76},
{0x80,0x80,0x80}, {0x8a,0x8a,0x8a}, {0x94,0x94,0x94}, {0x9e,0x9e,0x9e},
{0xa8,0xa8,0xa8}, {0xb2,0xb2,0xb2}, {0xbc,0xbc,0xbc}, {0xc6,0xc6,0xc6},
{0xd0,0xd0,0xd0}, {0xda,0xda,0xda}, {0xe4,0xe4,0xe4}, {0xee,0xee,0xee},
};
const std::initializer_list<HashMap<Kakoune::Color, int>::Item>
NCursesUI::Palette::default_colors = {
{ Color::Default, -1 },
{ Color::Black, 0 },
{ Color::Red, 1 },
{ Color::Green, 2 },
{ Color::Yellow, 3 },
{ Color::Blue, 4 },
{ Color::Magenta, 5 },
{ Color::Cyan, 6 },
{ Color::White, 7 },
{ Color::BrightBlack, 8 },
{ Color::BrightRed, 9 },
{ Color::BrightGreen, 10 },
{ Color::BrightYellow, 11 },
{ Color::BrightBlue, 12 },
{ Color::BrightMagenta, 13 },
{ Color::BrightCyan, 14 },
{ Color::BrightWhite, 15 },
};
int NCursesUI::Palette::get_color(Color color)
{
auto it = m_colors.find(color);
if (it != m_colors.end())
return it->value;
else if (m_change_colors and can_change_color() and COLORS > 16)
{
kak_assert(color.isRGB());
if (m_next_color > COLORS)
m_next_color = 16;
init_color(m_next_color,
color.r * 1000 / 255,
color.g * 1000 / 255,
color.b * 1000 / 255);
m_colors[color] = m_next_color;
return m_next_color++;
}
else
{
kak_assert(color.isRGB());
int lowestDist = INT_MAX;
int closestCol = -1;
for (int i = 0; i < std::min(256, COLORS); ++i)
{
auto& col = builtin_colors[i];
int dist = sq(color.r - col.r)
+ sq(color.g - col.g)
+ sq(color.b - col.b);
if (dist < lowestDist)
{
lowestDist = dist;
closestCol = i;
}
}
return closestCol;
}
}
int NCursesUI::Palette::get_color_pair(const Face& face)
{
ColorPair colors{face.fg, face.bg};
auto it = m_colorpairs.find(colors);
if (it != m_colorpairs.end())
return it->value;
else
{
init_pair(m_next_pair, get_color(face.fg), get_color(face.bg));
m_colorpairs[colors] = m_next_pair;
return m_next_pair++;
}
}
bool NCursesUI::Palette::set_change_colors(bool change_colors)
{
bool reset = false;
if (can_change_color() and m_change_colors != change_colors)
{
fputs("\033]104\007", stdout); // try to reset palette
fflush(stdout);
m_colorpairs.clear();
m_colors = default_colors;
m_next_color = 16;
m_next_pair = 1;
reset = true;
}
m_change_colors = change_colors;
return reset;
}
static sig_atomic_t resize_pending = 0; static sig_atomic_t resize_pending = 0;
static sig_atomic_t stdin_closed = 0; static sig_atomic_t stdin_closed = 0;
@ -347,12 +201,8 @@ NCursesUI::NCursesUI()
tcgetattr(STDIN_FILENO, &m_original_termios); tcgetattr(STDIN_FILENO, &m_original_termios);
initscr(); setup_terminal();
curs_set(0); set_raw_mode();
start_color();
use_default_colors();
set_terminal_mode();
enable_mouse(true); enable_mouse(true);
set_signal_handler(SIGWINCH, &signal_handler<&resize_pending>); set_signal_handler(SIGWINCH, &signal_handler<&resize_pending>);
@ -366,9 +216,8 @@ NCursesUI::NCursesUI()
NCursesUI::~NCursesUI() NCursesUI::~NCursesUI()
{ {
enable_mouse(false); enable_mouse(false);
m_palette.set_change_colors(false); restore_terminal();
restore_terminal_mode(); tcsetattr(STDIN_FILENO, TCSAFLUSH, &m_original_termios);
endwin();
set_signal_handler(SIGWINCH, SIG_DFL); set_signal_handler(SIGWINCH, SIG_DFL);
set_signal_handler(SIGHUP, SIG_DFL); set_signal_handler(SIGHUP, SIG_DFL);
set_signal_handler(SIGTSTP, SIG_DFL); set_signal_handler(SIGTSTP, SIG_DFL);
@ -378,16 +227,13 @@ void NCursesUI::suspend()
{ {
bool mouse_enabled = m_mouse_enabled; bool mouse_enabled = m_mouse_enabled;
enable_mouse(false); enable_mouse(false);
bool change_color_enabled = m_palette.get_change_colors(); restore_terminal();
m_palette.set_change_colors(false);
auto current = set_signal_handler(SIGTSTP, SIG_DFL); auto current = set_signal_handler(SIGTSTP, SIG_DFL);
sigset_t unblock_sigtstp, old_mask; sigset_t unblock_sigtstp, old_mask;
sigemptyset(&unblock_sigtstp); sigemptyset(&unblock_sigtstp);
sigaddset(&unblock_sigtstp, SIGTSTP); sigaddset(&unblock_sigtstp, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &unblock_sigtstp, &old_mask); sigprocmask(SIG_UNBLOCK, &unblock_sigtstp, &old_mask);
restore_terminal_mode();
endwin();
raise(SIGTSTP); // suspend here raise(SIGTSTP); // suspend here
@ -395,14 +241,15 @@ void NCursesUI::suspend()
set_signal_handler(SIGTSTP, current); set_signal_handler(SIGTSTP, current);
sigprocmask(SIG_SETMASK, &old_mask, nullptr); sigprocmask(SIG_SETMASK, &old_mask, nullptr);
doupdate(); setup_terminal();
check_resize(true); check_resize(true);
set_terminal_mode(); set_raw_mode();
m_palette.set_change_colors(change_color_enabled);
enable_mouse(mouse_enabled); enable_mouse(mouse_enabled);
refresh(true);
} }
void NCursesUI::set_terminal_mode() const void NCursesUI::set_raw_mode() const
{ {
termios attr = m_original_termios; termios attr = m_original_termios;
attr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); attr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
@ -414,24 +261,6 @@ void NCursesUI::set_terminal_mode() const
attr.c_cc[VMIN] = attr.c_cc[VTIME] = 0; attr.c_cc[VMIN] = attr.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &attr); tcsetattr(STDIN_FILENO, TCSANOW, &attr);
fputs("\033=", stdout);
// force enable report focus events
fputs("\033[?1004h", stdout);
// request CSI u style key reporting
fputs("\033[>4;1m", stdout);
// save the current window title
fputs("\033[22t", stdout);
fflush(stdout);
}
void NCursesUI::restore_terminal_mode() const
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &m_original_termios);
fputs("\033>", stdout);
fputs("\033[?1004l", stdout);
fputs("\033[>4;0m", stdout);
fputs("\033[23t", stdout);
fflush(stdout);
} }
void NCursesUI::redraw(bool force) void NCursesUI::redraw(bool force)
@ -443,13 +272,12 @@ void NCursesUI::redraw(bool force)
m_info.refresh(false); m_info.refresh(false);
Window screen{{}, static_cast<NCursesWin*>(newscr)};
if (m_cursor.mode == CursorMode::Prompt) if (m_cursor.mode == CursorMode::Prompt)
screen.move_cursor({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}); set_cursor_pos({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column});
else else
screen.move_cursor(m_cursor.coord + content_line_offset()); set_cursor_pos(m_cursor.coord + content_line_offset());
doupdate(); fflush(stdout);
} }
void NCursesUI::set_cursor(CursorMode mode, DisplayCoord coord) void NCursesUI::set_cursor(CursorMode mode, DisplayCoord coord)
@ -478,24 +306,17 @@ void NCursesUI::draw(const DisplayBuffer& display_buffer,
for (const DisplayLine& line : display_buffer.lines()) for (const DisplayLine& line : display_buffer.lines())
{ {
m_window.move_cursor(line_index++); m_window.move_cursor(line_index++);
m_window.draw(m_palette, line.atoms(), default_face); m_window.draw(line.atoms(), default_face);
} }
auto face = merge_faces(default_face, padding_face); auto face = merge_faces(default_face, padding_face);
while (line_index < dim.line + line_offset) while (line_index < dim.line + line_offset)
{ {
m_window.move_cursor(line_index++); m_window.move_cursor(line_index++);
if (m_padding_fill) m_window.draw(m_padding_char, face);
{ const auto padding_len = m_padding_char.length();
ColumnCount column_index = 0; for (auto col = padding_len; m_padding_fill and col < dim.column; col += padding_len)
while (column_index < dim.column) m_window.draw(m_padding_char, face);
{
m_window.draw(m_palette, m_padding_char, face);
column_index += m_padding_char.length();
}
}
else
m_window.draw(m_palette, m_padding_char, face);
} }
m_dirty = true; m_dirty = true;
@ -508,7 +329,7 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
const LineCount status_line_pos = m_status_on_top ? 0 : m_dimensions.line; const LineCount status_line_pos = m_status_on_top ? 0 : m_dimensions.line;
m_window.move_cursor(status_line_pos); m_window.move_cursor(status_line_pos);
m_window.draw(m_palette, status_line.atoms(), default_face); m_window.draw(status_line.atoms(), default_face);
const auto mode_len = mode_line.length(); const auto mode_len = mode_line.length();
m_status_len = status_line.length(); m_status_len = status_line.length();
@ -517,7 +338,7 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
{ {
ColumnCount col = m_dimensions.column - mode_len; ColumnCount col = m_dimensions.column - mode_len;
m_window.move_cursor({status_line_pos, col}); m_window.move_cursor({status_line_pos, col});
m_window.draw(m_palette, mode_line.atoms(), default_face); m_window.draw(mode_line.atoms(), default_face);
} }
else if (remaining > 2) else if (remaining > 2)
{ {
@ -528,7 +349,7 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
ColumnCount col = m_dimensions.column - remaining + 1; ColumnCount col = m_dimensions.column - remaining + 1;
m_window.move_cursor({status_line_pos, col}); m_window.move_cursor({status_line_pos, col});
m_window.draw(m_palette, trimmed_mode_line.atoms(), default_face); m_window.draw(trimmed_mode_line.atoms(), default_face);
} }
if (m_set_title) if (m_set_title)
@ -576,15 +397,13 @@ void NCursesUI::check_resize(bool force)
if (info) m_info.destroy(); if (info) m_info.destroy();
if (menu) m_menu.destroy(); if (menu) m_menu.destroy();
resize_term(ws.ws_row, ws.ws_col);
m_window.create({0, 0}, {ws.ws_row, ws.ws_col}); m_window.create({0, 0}, {ws.ws_row, ws.ws_col});
kak_assert(m_window); kak_assert(m_window);
m_dimensions = DisplayCoord{ws.ws_row-1, ws.ws_col}; m_dimensions = DisplayCoord{ws.ws_row-1, ws.ws_col};
if (char* csr = tigetstr((char*)"csr")) // if (char* csr = tigetstr((char*)"csr"))
putp(tparm(csr, 0, ws.ws_row)); // putp(tparm(csr, 0, ws.ws_row));
if (menu) if (menu)
menu_show(Vector<DisplayLine>(std::move(m_menu.items)), menu_show(Vector<DisplayLine>(std::move(m_menu.items)),
@ -593,7 +412,6 @@ void NCursesUI::check_resize(bool force)
info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style); info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style);
set_resize_pending(); set_resize_pending();
wclear(curscr);
} }
Optional<Key> NCursesUI::get_next_key() Optional<Key> NCursesUI::get_next_key()
@ -897,7 +715,7 @@ void NCursesUI::draw_menu()
ColumnCount pos = 0; ColumnCount pos = 0;
m_menu.move_cursor({0, 0}); m_menu.move_cursor({0, 0});
m_menu.draw(m_palette, DisplayAtom(m_menu.first_item > 0 ? "< " : " "), m_menu.bg); m_menu.draw(DisplayAtom(m_menu.first_item > 0 ? "< " : " "), m_menu.bg);
int i = m_menu.first_item; int i = m_menu.first_item;
for (; i < item_count and pos < win_width; ++i) for (; i < item_count and pos < win_width; ++i)
@ -905,19 +723,19 @@ void NCursesUI::draw_menu()
const DisplayLine& item = m_menu.items[i]; const DisplayLine& item = m_menu.items[i];
const ColumnCount item_width = item.length(); const ColumnCount item_width = item.length();
auto& face = i == m_menu.selected_item ? m_menu.fg : m_menu.bg; auto& face = i == m_menu.selected_item ? m_menu.fg : m_menu.bg;
m_menu.draw(m_palette, item.atoms(), face); m_menu.draw(item.atoms(), face);
if (pos + item_width < win_width) if (pos + item_width < win_width)
m_menu.draw(m_palette, DisplayAtom(" "), m_menu.bg); m_menu.draw(DisplayAtom(" "), m_menu.bg);
else else
{ {
m_menu.move_cursor({0, win_width+2}); m_menu.move_cursor({0, win_width+2});
m_menu.draw(m_palette, DisplayAtom(""), m_menu.bg); m_menu.draw(DisplayAtom(""), m_menu.bg);
} }
pos += item_width + 1; pos += item_width + 1;
} }
m_menu.move_cursor({0, win_width+3}); m_menu.move_cursor({0, win_width+3});
m_menu.draw(m_palette, DisplayAtom(i == item_count ? " " : ">"), m_menu.bg); m_menu.draw(DisplayAtom(i == item_count ? " " : ">"), m_menu.bg);
m_dirty = true; m_dirty = true;
return; return;
@ -945,11 +763,11 @@ void NCursesUI::draw_menu()
int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line; int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line;
auto& face = item_idx < item_count and item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg; auto& face = item_idx < item_count and item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg;
auto atoms = item_idx < item_count ? m_menu.items[item_idx].atoms() : ConstArrayView<DisplayAtom>{}; auto atoms = item_idx < item_count ? m_menu.items[item_idx].atoms() : ConstArrayView<DisplayAtom>{};
m_menu.draw(m_palette, atoms, face); m_menu.draw(atoms, face);
} }
const bool is_mark = line >= mark_line and line < mark_line + mark_height; const bool is_mark = line >= mark_line and line < mark_line + mark_height;
m_menu.move_cursor({line, m_menu.size.column - 1}); m_menu.move_cursor({line, m_menu.size.column - 1});
m_menu.draw(m_palette, DisplayAtom(is_mark ? "" : ""), m_menu.bg); m_menu.draw(DisplayAtom(is_mark ? "" : ""), m_menu.bg);
} }
m_dirty = true; m_dirty = true;
} }
@ -1245,8 +1063,8 @@ void NCursesUI::info_show(const DisplayLine& title, const DisplayLineList& conte
m_info.create(anchor, size); m_info.create(anchor, size);
auto draw_atoms = [&](auto&&... args) { auto draw_atoms = [&](auto&&... args) {
auto draw = overload( auto draw = overload(
[&](String str) { m_info.draw(m_palette, DisplayAtom{std::move(str)}, face); }, [&](String str) { m_info.draw(DisplayAtom{std::move(str)}, face); },
[&](const DisplayLine& atoms) { m_info.draw(m_palette, atoms.atoms(), face); }); [&](const DisplayLine& atoms) { m_info.draw(atoms.atoms(), face); });
(draw(args), ...); (draw(args), ...);
}; };
@ -1324,9 +1142,35 @@ void NCursesUI::set_resize_pending()
EventManager::instance().force_signal(0); EventManager::instance().force_signal(0);
} }
void NCursesUI::abort() void NCursesUI::setup_terminal()
{ {
endwin(); // enable alternative screen buffer
fputs("\033[?1049h", stdout);
// enable focus notify
fputs("\033[?1004h", stdout);
// request CSI u style key reporting
fputs("\033[>4;1m", stdout);
// save the current window title
fputs("\033[22t", stdout);
// hide cursor
fputs("\033[?25l", stdout);
// set application keypad mode, so the keypad keys send unique codes
fputs("\033=", stdout);
fflush(stdout);
}
void NCursesUI::restore_terminal()
{
fputs("\033>", stdout);
fputs("\033[?25h", stdout);
fputs("\033[23t", stdout);
fputs("\033[>4;0m", stdout);
fputs("\033[?1004l", stdout);
fputs("\033[?1049l", stdout);
// set the terminal output back to default colours and style
fputs("\033[m", stdout);
fflush(stdout);
} }
void NCursesUI::enable_mouse(bool enabled) void NCursesUI::enable_mouse(bool enabled)
@ -1386,17 +1230,6 @@ void NCursesUI::set_ui_options(const Options& options)
: default_shift_function_key; : default_shift_function_key;
} }
{
auto it = options.find("ncurses_change_colors"_sv);
if (m_palette.set_change_colors(it == options.end() or
(it->value == "yes" or it->value == "true")))
{
m_window.m_active_pair = -1;
m_menu.m_active_pair = -1;
m_info.m_active_pair = -1;
}
}
{ {
auto enable_mouse_it = options.find("ncurses_enable_mouse"_sv); auto enable_mouse_it = options.find("ncurses_enable_mouse"_sv);
enable_mouse(enable_mouse_it == options.end() or enable_mouse(enable_mouse_it == options.end() or

View File

@ -18,8 +18,6 @@ namespace Kakoune
struct DisplayAtom; struct DisplayAtom;
struct NCursesWin;
class NCursesUI : public UserInterface, public Singleton<NCursesUI> class NCursesUI : public UserInterface, public Singleton<NCursesUI>
{ {
public: public:
@ -58,7 +56,8 @@ public:
void set_on_key(OnKeyCallback callback) override; void set_on_key(OnKeyCallback callback) override;
void set_ui_options(const Options& options) override; void set_ui_options(const Options& options) override;
static void abort(); static void setup_terminal();
static void restore_terminal();
void suspend(); void suspend();
@ -74,41 +73,25 @@ private:
Optional<Key> get_next_key(); Optional<Key> get_next_key();
struct Palette
{
private:
static const std::initializer_list<HashMap<Kakoune::Color, int>::Item> default_colors;
using ColorPair = std::pair<Color, Color>;
HashMap<Color, int, MemoryDomain::Faces> m_colors = default_colors;
HashMap<ColorPair, int, MemoryDomain::Faces> m_colorpairs;
int m_next_color = 16;
int m_next_pair = 1;
bool m_change_colors = true;
int get_color(Color color);
public:
int get_color_pair(const Face& face);
bool get_change_colors() const { return m_change_colors; }
bool set_change_colors(bool change_colors);
};
Palette m_palette;
struct Window : Rect struct Window : Rect
{ {
void create(const DisplayCoord& pos, const DisplayCoord& size); void create(const DisplayCoord& pos, const DisplayCoord& size);
void destroy(); void destroy();
void refresh(bool force); void refresh(bool force);
void move_cursor(DisplayCoord coord); void move_cursor(DisplayCoord coord);
void draw(Palette& palette, ConstArrayView<DisplayAtom> atoms, void draw(ConstArrayView<DisplayAtom> atoms, const Face& default_face);
const Face& default_face);
explicit operator bool() const { return win; } explicit operator bool() const { return not lines.empty(); }
NCursesWin* win = nullptr; struct Atom
int m_active_pair = -1; {
String text;
Face face;
};
Vector<Vector<Atom>> lines;
DisplayCoord cursor;
void clear_line();
}; };
Window m_window; Window m_window;
@ -116,8 +99,7 @@ private:
DisplayCoord m_dimensions; DisplayCoord m_dimensions;
termios m_original_termios{}; termios m_original_termios{};
void set_terminal_mode() const; void set_raw_mode() const;
void restore_terminal_mode() const;
struct Menu : Window struct Menu : Window
{ {