diff --git a/README.asciidoc b/README.asciidoc index 99075ad6..b446bf7b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -407,7 +407,10 @@ there are some builtins color aliases: * +LineNumbers+: colors used by the number_lines highlighter * +MenuForeground+: colors for the selected element in menus * +MenuBackground+: colors for the not selected elements in menus - * +Information+: colors the informations windows + * +Information+: colors the informations windows and information messages + * +Error+: colors of error messages + * +StatusLine+: colors used for the status line + * +StatusCursor+: colors used for the status line cursor Shell expansion --------------- diff --git a/src/client_manager.cc b/src/client_manager.cc index a7919cf1..fc95c118 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -4,6 +4,7 @@ #include "buffer_manager.hh" #include "command_manager.hh" #include "file.hh" +#include "color_registry.hh" namespace Kakoune { @@ -64,7 +65,7 @@ void ClientManager::create_client(std::unique_ptr&& ui, } catch (Kakoune::runtime_error& error) { - context->print_status(error.description()); + context->print_status({ error.description(), get_color("Error") }); context->hooks().run_hook("RuntimeError", error.description(), *context); } catch (Kakoune::client_removed&) @@ -81,7 +82,7 @@ void ClientManager::create_client(std::unique_ptr&& ui, } catch (Kakoune::runtime_error& error) { - context->print_status(error.description()); + context->print_status({ error.description(), get_color("Error") }); context->hooks().run_hook("RuntimeError", error.description(), *context); } catch (Kakoune::client_removed&) @@ -191,7 +192,7 @@ Context& ClientManager::get_client_context(const String& name) throw runtime_error("no client named: " + name); } -static String generate_status_line(const Context& context) +static DisplayLine generate_status_line(const Context& context) { BufferCoord cursor = context.editor().main_selection().last().coord(); std::ostringstream oss; @@ -207,7 +208,7 @@ static String generate_status_line(const Context& context) oss << " [" << context.editor().selections().size() << " sel]"; if (context.editor().is_editing()) oss << " [insert]"; - return oss.str(); + return { oss.str(), get_color("StatusLine") }; } void ClientManager::redraw_clients() const diff --git a/src/color_registry.cc b/src/color_registry.cc index c7684691..b30afb28 100644 --- a/src/color_registry.cc +++ b/src/color_registry.cc @@ -49,6 +49,9 @@ ColorRegistry::ColorRegistry() { "MenuForeground", { Color::Blue, Color::Cyan } }, { "MenuBackground", { Color::Cyan, Color::Blue } }, { "Information", { Color::Black, Color::Yellow } }, + { "Error", { Color::Black, Color::Red } }, + { "StatusLine", { Color::Cyan, Color::Default } }, + { "StatusCursor", { Color::Black, Color::Cyan } }, } {} diff --git a/src/commands.cc b/src/commands.cc index 819cebb7..706a4dfd 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -37,7 +37,7 @@ Buffer* open_or_create(const String& filename, Context& context) Buffer* buffer = create_buffer_from_file(filename); if (not buffer) { - context.print_status("new file " + filename); + context.print_status({ "new file " + filename, get_color("StatusLine") }); buffer = new Buffer(filename, Buffer::Flags::File | Buffer::Flags::New); } return buffer; @@ -424,7 +424,7 @@ void echo_message(const CommandParameters& params, Context& context) String message; for (auto& param : params) message += param + " "; - context.print_status(message); + context.print_status({ std::move(message), get_color("StatusLine") } ); } void exec_commands_in_file(const CommandParameters& params, @@ -803,8 +803,8 @@ public: } bool is_key_available() override { return m_pos < m_keys.size(); } - void print_status(const String& , CharCount) override {} - void draw(const DisplayBuffer&, const String&) override {} + void print_status(const DisplayLine&) override {} + void draw(const DisplayBuffer&, const DisplayLine&) override {} void menu_show(const memoryview&, DisplayCoord, ColorPair, ColorPair, MenuStyle) override {} void menu_select(int) override {} diff --git a/src/context.hh b/src/context.hh index bfbc6c6d..c6c7abc2 100644 --- a/src/context.hh +++ b/src/context.hh @@ -95,7 +95,7 @@ struct Context return GlobalHooks::instance(); } - void print_status(const String& status) const + void print_status(const DisplayLine& status) const { if (has_ui()) ui().print_status(status); diff --git a/src/display_buffer.cc b/src/display_buffer.cc index d9ab9609..f518ab50 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -42,6 +42,14 @@ void DisplayLine::optimize() } } +CharCount DisplayLine::length() const +{ + CharCount len = 0; + for (auto& atom : m_atoms) + len += atom.content.length(); + return len; +} + void DisplayBuffer::compute_range() { m_range.first = BufferIterator(); diff --git a/src/display_buffer.hh b/src/display_buffer.hh index 3879a31e..49bf67cb 100644 --- a/src/display_buffer.hh +++ b/src/display_buffer.hh @@ -112,12 +112,13 @@ struct DisplayAtom { ColorPair colors; Attribute attribute; - AtomContent content; - DisplayAtom(AtomContent content) - : content{std::move(content)}, attribute{Normal}, - colors{Color::Default, Color::Default} {} + DisplayAtom(AtomContent content, + ColorPair colors = {Color::Default, Color::Default}, + Attribute attribute = Normal) + : content{std::move(content)}, colors{colors}, attribute{attribute} + {} }; class DisplayLine @@ -130,6 +131,8 @@ public: explicit DisplayLine(LineCount buffer_line) : m_buffer_line(buffer_line) {} DisplayLine(LineCount buffer_line, AtomList atoms) : m_buffer_line(buffer_line), m_atoms(std::move(atoms)) {} + DisplayLine(String str, ColorPair color) + : m_buffer_line(-1), m_atoms{ { std::move(str), color } } {} LineCount buffer_line() const { return m_buffer_line; } @@ -141,6 +144,8 @@ public: const AtomList& atoms() const { return m_atoms; } + CharCount length() const; + // Split atom pointed by it at pos, returns an iterator to the first atom iterator split(iterator it, BufferIterator pos); diff --git a/src/input_handler.cc b/src/input_handler.cc index f8a2fc64..cb9cabbf 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -144,6 +144,18 @@ private: String m_line; }; +static DisplayLine line_with_cursor(const String& str, CharCount cursor_pos) +{ + assert(cursor_pos <= str.char_length()); + if (cursor_pos == str.char_length()) + return DisplayLine{-1, { {str, get_color("StatusLine")}, + {" "_str, get_color("StatusCursor")} }}; + else + return DisplayLine(-1, { DisplayAtom{ str.substr(0, cursor_pos), get_color("StatusLine") }, + DisplayAtom{ str.substr(cursor_pos, 1), get_color("StatusCursor") }, + DisplayAtom{ str.substr(cursor_pos+1), get_color("StatusLine") } }); +} + class Menu : public InputMode { public: @@ -167,7 +179,7 @@ public: if (key == Key(Key::Modifiers::Control, 'm')) { context().ui().menu_hide(); - context().ui().print_status(""); + context().ui().print_status(DisplayLine{ -1 }); reset_normal_mode(); int selected = m_selected - m_choices.begin(); m_callback(selected, MenuEvent::Validate, context()); @@ -180,7 +192,7 @@ public: m_edit_filter = false; m_filter = boost::regex(".*"); m_filter_editor.reset(""); - context().ui().print_status(""); + context().ui().print_status(DisplayLine{ -1 }); } else { @@ -228,8 +240,8 @@ public: } if (m_edit_filter) - context().ui().print_status("/" + m_filter_editor.line(), - m_filter_editor.cursor_pos() + 1); + context().ui().print_status(line_with_cursor("/" + m_filter_editor.line(), + m_filter_editor.cursor_pos() + 1)); } private: @@ -279,7 +291,7 @@ public: m_completer(completer), m_callback(callback) { m_history_it = ms_history[m_prompt].end(); - context().ui().print_status(m_prompt, m_prompt.char_length()); + context().ui().print_status(line_with_cursor(m_prompt, m_prompt.char_length())); } void on_key(const Key& key) override @@ -302,7 +314,7 @@ public: history.erase(it); history.push_back(line); } - context().ui().print_status(""); + context().ui().print_status(DisplayLine{ -1 }); context().ui().menu_hide(); reset_normal_mode(); // call callback after reset_normal_mode so that callback @@ -312,7 +324,7 @@ public: } else if (key == Key::Escape or key == Key { Key::Modifiers::Control, 'c' }) { - context().ui().print_status(""); + context().ui().print_status(DisplayLine{ -1 }); context().ui().menu_hide(); reset_normal_mode(); m_callback(line, PromptEvent::Abort, context()); @@ -417,8 +429,8 @@ public: m_current_completion = -1; m_line_editor.handle_key(key); } - context().ui().print_status(m_prompt + line, - m_prompt.char_length() + m_line_editor.cursor_pos()); + auto curpos = m_prompt.char_length() + m_line_editor.cursor_pos(); + context().ui().print_status(line_with_cursor(m_prompt + line, curpos)); m_callback(line, PromptEvent::Change, context()); } diff --git a/src/main.cc b/src/main.cc index bb63be0e..932fad61 100644 --- a/src/main.cc +++ b/src/main.cc @@ -223,7 +223,7 @@ void do_search_next(Context& context) } while (--count > 0); } else - context.print_status("no search pattern"); + context.print_status({ "no search pattern", get_color("Error") }); } template @@ -255,7 +255,7 @@ void use_selection_as_search_pattern(Context& context) void do_yank(Context& context) { RegisterManager::instance()['"'] = context.editor().selections_content(); - context.print_status("yanked " + int_to_str(context.editor().selections().size()) + " selections"); + context.print_status({ "yanked " + int_to_str(context.editor().selections().size()) + " selections", get_color("Information") }); } void do_cat_yank(Context& context) @@ -265,8 +265,8 @@ void do_cat_yank(Context& context) for (auto& sel : sels) str += sel; RegisterManager::instance()['"'] = memoryview(str); - context.print_status("concatenated and yanked " + - int_to_str(sels.size()) + " selections"); + context.print_status({ "concatenated and yanked " + + int_to_str(sels.size()) + " selections", get_color("Information") }); } void do_erase(Context& context) @@ -716,8 +716,8 @@ std::unordered_map> keymap = { { Key::Modifiers::None, '*' }, use_selection_as_search_pattern }, { { Key::Modifiers::Alt, '*' }, use_selection_as_search_pattern }, - { { Key::Modifiers::None, 'u' }, repeated([](Context& context) { if (not context.editor().undo()) { context.print_status("nothing left to undo"); } }) }, - { { Key::Modifiers::None, 'U' }, repeated([](Context& context) { if (not context.editor().redo()) { context.print_status("nothing left to redo"); } }) }, + { { Key::Modifiers::None, 'u' }, repeated([](Context& context) { if (not context.editor().undo()) { context.print_status({ "nothing left to undo", get_color("Information") }); } }) }, + { { Key::Modifiers::None, 'U' }, repeated([](Context& context) { if (not context.editor().redo()) { context.print_status({ "nothing left to redo", get_color("Information") }); } }) }, { { Key::Modifiers::Alt, 'i' }, do_select_object }, { { Key::Modifiers::Alt, 'a' }, do_select_object }, diff --git a/src/ncurses.cc b/src/ncurses.cc index 120b9876..1425c47c 100644 --- a/src/ncurses.cc +++ b/src/ncurses.cc @@ -86,7 +86,8 @@ void on_sigint(int) } NCursesUI::NCursesUI() - : m_stdin_watcher{0, [this](FDWatcher&){ if (m_input_callback) m_input_callback(); }} + : m_stdin_watcher{0, [this](FDWatcher&){ if (m_input_callback) m_input_callback(); }}, + m_status_line{-1} { initscr(); cbreak(); @@ -154,40 +155,44 @@ void NCursesUI::update_dimensions() --m_dimensions.line; } +void NCursesUI::draw_line(const DisplayLine& line, CharCount col_index) const +{ + for (const DisplayAtom& atom : line) + { + set_attribute(A_UNDERLINE, atom.attribute & Underline); + set_attribute(A_REVERSE, atom.attribute & Reverse); + set_attribute(A_BLINK, atom.attribute & Blink); + set_attribute(A_BOLD, atom.attribute & Bold); + + set_color(stdscr, atom.colors); + + String content = atom.content.content(); + if (content[content.length()-1] == '\n' and + content.char_length() - 1 < m_dimensions.column - col_index) + { + addutf8str(stdscr, Utf8Iterator(content.begin()), Utf8Iterator(content.end())-1); + addch(' '); + } + else + { + Utf8Iterator begin(content.begin()), end(content.end()); + if (end - begin > m_dimensions.column - col_index) + end = begin + (m_dimensions.column - col_index); + addutf8str(stdscr, begin, end); + col_index += end - begin; + } + } +} + void NCursesUI::draw(const DisplayBuffer& display_buffer, - const String& mode_line) + const DisplayLine& mode_line) { LineCount line_index = 0; for (const DisplayLine& line : display_buffer.lines()) { wmove(stdscr, (int)line_index, 0); wclrtoeol(stdscr); - CharCount col_index = 0; - for (const DisplayAtom& atom : line) - { - set_attribute(A_UNDERLINE, atom.attribute & Underline); - set_attribute(A_REVERSE, atom.attribute & Reverse); - set_attribute(A_BLINK, atom.attribute & Blink); - set_attribute(A_BOLD, atom.attribute & Bold); - - set_color(stdscr, atom.colors); - - String content = atom.content.content(); - if (content[content.length()-1] == '\n' and - content.char_length() - 1 < m_dimensions.column - col_index) - { - addutf8str(stdscr, Utf8Iterator(content.begin()), Utf8Iterator(content.end())-1); - addch(' '); - } - else - { - Utf8Iterator begin(content.begin()), end(content.end()); - if (end - begin > m_dimensions.column - col_index) - end = begin + (m_dimensions.column - col_index); - addutf8str(stdscr, begin, end); - col_index += end - begin; - } - } + draw_line(line, 0); ++line_index; } @@ -203,15 +208,16 @@ void NCursesUI::draw(const DisplayBuffer& display_buffer, addch('~'); } - set_color(stdscr, { Color::Cyan, Color::Default }); - draw_status(); - CharCount status_len = mode_line.char_length(); + move((int)m_dimensions.line, 0); + clrtoeol(); + draw_line(m_status_line, 0); + CharCount status_len = mode_line.length(); // only draw mode_line if it does not overlap one status line - if (m_dimensions.column - m_status_line.char_length() > status_len + 1) + if (m_dimensions.column - m_status_line.length() > status_len + 1) { - move((int)m_dimensions.line, (int)(m_dimensions.column - status_len)); - addutf8str(stdscr, Utf8Iterator(mode_line.begin()), - Utf8Iterator(mode_line.end())); + CharCount col = m_dimensions.column - status_len; + move((int)m_dimensions.line, (int)col); + draw_line(mode_line, col); } redraw(); } @@ -285,36 +291,12 @@ Key NCursesUI::get_key() return Key::Invalid; } -void NCursesUI::draw_status() -{ - move((int)m_dimensions.line, 0); - clrtoeol(); - if (m_status_cursor == -1) - addutf8str(stdscr, m_status_line.cbegin(), m_status_line.cend()); - else - { - Utf8Iterator begin{m_status_line.begin()}; - Utf8Iterator end{m_status_line.end()}; - Utf8Iterator cursor_it{begin}; - cursor_it.advance(m_status_cursor, end); - - addutf8str(stdscr, m_status_line.cbegin(), cursor_it); - set_attribute(A_REVERSE, 1); - if (cursor_it == end) - addch(' '); - else - addutf8str(stdscr, cursor_it, cursor_it+1); - set_attribute(A_REVERSE, 0); - if (cursor_it != end) - addutf8str(stdscr, cursor_it+1, end); - } -} - -void NCursesUI::print_status(const String& status, CharCount cursor_pos) +void NCursesUI::print_status(const DisplayLine& status) { m_status_line = status; - m_status_cursor = cursor_pos; - draw_status(); + move((int)m_dimensions.line, 0); + clrtoeol(); + draw_line(status, 0); redraw(); } diff --git a/src/ncurses.hh b/src/ncurses.hh index 633ff110..de4f1dc6 100644 --- a/src/ncurses.hh +++ b/src/ncurses.hh @@ -20,8 +20,8 @@ public: NCursesUI& operator=(const NCursesUI&) = delete; void draw(const DisplayBuffer& display_buffer, - const String& mode_line) override; - void print_status(const String& status, CharCount cursor_pos) override; + const DisplayLine& mode_line) override; + void print_status(const DisplayLine& status) override; bool is_key_available() override; Key get_key() override; @@ -42,13 +42,12 @@ public: private: friend void on_term_resize(int); void redraw(); + void draw_line(const DisplayLine& line, CharCount col_index) const; DisplayCoord m_dimensions; void update_dimensions(); - String m_status_line; - CharCount m_status_cursor = -1; - void draw_status(); + DisplayLine m_status_line; WINDOW* m_menu_win = nullptr; std::vector m_choices; diff --git a/src/remote.cc b/src/remote.cc index 8c14420e..01b6760b 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -186,7 +186,7 @@ public: RemoteUI(int socket); ~RemoteUI(); - void print_status(const String& status, CharCount cursor_pos) override; + void print_status(const DisplayLine& status) override; void menu_show(const memoryview& choices, DisplayCoord anchor, ColorPair fg, ColorPair bg, @@ -199,7 +199,7 @@ public: void info_hide() override; void draw(const DisplayBuffer& display_buffer, - const String& mode_line) override; + const DisplayLine& mode_line) override; bool is_key_available() override; Key get_key() override; @@ -226,12 +226,11 @@ RemoteUI::~RemoteUI() close(m_socket_watcher.fd()); } -void RemoteUI::print_status(const String& status, CharCount cursor_pos) +void RemoteUI::print_status(const DisplayLine& status) { Message msg(m_socket_watcher.fd()); msg.write(RemoteUIMsg::PrintStatus); msg.write(status); - msg.write(cursor_pos); } void RemoteUI::menu_show(const memoryview& choices, @@ -278,7 +277,7 @@ void RemoteUI::info_hide() } void RemoteUI::draw(const DisplayBuffer& display_buffer, - const String& mode_line) + const DisplayLine& mode_line) { Message msg(m_socket_watcher.fd()); msg.write(RemoteUIMsg::Draw); @@ -345,9 +344,8 @@ void RemoteClient::process_next_message() { case RemoteUIMsg::PrintStatus: { - auto status = read(socket); - auto cursor_pos = read(socket); - m_ui->print_status(status, cursor_pos); + auto status = read(socket); + m_ui->print_status(status); break; } case RemoteUIMsg::MenuShow: @@ -380,8 +378,8 @@ void RemoteClient::process_next_message() break; case RemoteUIMsg::Draw: { - DisplayBuffer display_buffer = read(socket); - String mode_line = read(socket); + auto display_buffer = read(socket); + auto mode_line = read(socket); m_ui->draw(display_buffer, mode_line); break; } diff --git a/src/user_interface.hh b/src/user_interface.hh index 8fa99c4a..522357ab 100644 --- a/src/user_interface.hh +++ b/src/user_interface.hh @@ -12,6 +12,7 @@ namespace Kakoune class String; class DisplayBuffer; +class DisplayLine; struct DisplayCoord; enum class MenuStyle @@ -26,7 +27,7 @@ class UserInterface : public SafeCountable { public: virtual ~UserInterface() {} - virtual void print_status(const String& status, CharCount cursor_pos = -1) = 0; + virtual void print_status(const DisplayLine& status) = 0; virtual void menu_show(const memoryview& choices, DisplayCoord anchor, ColorPair fg, ColorPair bg, @@ -39,7 +40,7 @@ public: virtual void info_hide() = 0; virtual void draw(const DisplayBuffer& display_buffer, - const String& mode_line) = 0; + const DisplayLine& mode_line) = 0; virtual DisplayCoord dimensions() = 0; virtual bool is_key_available() = 0; virtual Key get_key() = 0;