Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ This configuration file holds general user interface settings. Default content:
typing_status_share=1
undo_clear_input=1
unread_indicator=*
vim_mode=0

### attachment_indicator

Expand Down Expand Up @@ -850,6 +851,33 @@ is enabled.

Specifies the character to suffix chats with unread messages in the chat list.

### vim_mode

Specifies whether to enable vim-style modal editing in the message compose
entry. Default `0` (disabled). When enabled, the entry starts in insert mode;
press `Esc` for normal mode. The current mode is shown as a colored badge in
the status bar (configurable via the `vim_*_color` / `vim_*_attr` keys in
`color.conf`) and as a cursor shape (bar in insert, block in normal).

Supported commands in normal mode:

| Group | Commands |
| ----- | -------- |
| Motions | `h l 0 ^ $`, `w e b W E B`, `( )` (sentence), `{ }` (paragraph), `j k`, `gg G`, `f F t T` |
| Operators | `d c y` + any motion; `dd cc yy` (line); `D C` (to end of line) |
| Edit | `x X`, `s S` (substitute), `o O` (open line), `p P` (paste) |
| Modes | `i a A I` (insert), `v` (visual), `Esc` (normal) |
| Counts | e.g. `3w`, `d3w`, `2dd` |

Visual mode (`v`) highlights the selection and applies `d c y x` to it.
When `vim_mode=0` there is no behavioral change and zero overhead.

### send_on_double_enter

Set `send_on_double_enter=1` in `ui.conf` (default `0`) to make a single
`Enter` insert a newline and a second consecutive `Enter` send the message
(dropping the just-typed newline). The existing `send_msg` key still works.

~/.config/nchat/key.conf
------------------------
This configuration file holds user interface key bindings. Default content:
Expand Down Expand Up @@ -986,6 +1014,16 @@ This configuration file holds user interface color settings. Default content:
top_color_bg=
top_color_fg=

Vim mode badge colors (`vim_*_color_bg`, `vim_*_color_fg`, `vim_*_attr`):

| Prefix | Default badge |
| ------ | ------------- |
| `vim_normal_*` | bright-blue bg / black fg (bold) |
| `vim_insert_*` | bright-green bg / black fg (bold) |
| `vim_visual_*` | bright-yellow bg / black fg (bold) |

Conventional vim/airline mode colors. Configurable via `vim_normal_*` / `vim_insert_*` / `vim_visual_*` in `color.conf`.

Supported text attributes `_attr` (defaults to `normal` if not specified):

normal
Expand Down
1 change: 1 addition & 0 deletions src/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void Ui::Init()
cbreak();
UiConfig::GetBool("linefeed_on_enter") ? nl() : nonl();
keypad(stdscr, TRUE);
set_escdelay(25); // minimize lag on bare ESC (vim_mode mode switch)
curs_set(0);
timeout(0);
EmojiList::Init();
Expand Down
10 changes: 10 additions & 0 deletions src/uicolorconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ void UiColorConfig::Init()
{ "status_attr", "reverse" },
{ "status_color_bg", "" },
{ "status_color_fg", "" },
// Conventional vim/airline mode colors (NORMAL blue, INSERT green, VISUAL yellow).
{ "vim_normal_attr", "bold" },
{ "vim_normal_color_bg", "bright_blue" },
{ "vim_normal_color_fg", "black" },
{ "vim_insert_attr", "bold" },
{ "vim_insert_color_bg", "bright_green" },
{ "vim_insert_color_fg", "black" },
{ "vim_visual_attr", "bold" },
{ "vim_visual_color_bg", "bright_yellow" },
{ "vim_visual_color_fg", "black" },
{ "list_attr", "" },
{ "list_attr_selected", "reverse" },
{ "list_color_bg", "" },
Expand Down
2 changes: 2 additions & 0 deletions src/uiconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ void UiConfig::Init()
{ "online_status_share", "1" },
{ "online_status_dynamic", "1" },
{ "phone_number_indicator", "" },
{ "send_on_double_enter", "0" },
{ "pinned_indicator", "\xe2\x9a\xb2" },
{ "proxy_indicator", "\xF0\x9F\x94\x92" },
{ "read_indicator", "\xe2\x9c\x93" },
Expand All @@ -80,6 +81,7 @@ void UiConfig::Init()
{ "typing_status_share", "1" },
{ "undo_clear_input", "1" },
{ "unread_indicator", "*" },
{ "vim_mode", "0" },
};

const std::string configPath(FileUtil::GetApplicationDir() + std::string("/ui.conf"));
Expand Down
44 changes: 42 additions & 2 deletions src/uientryview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ void UiEntryView::Draw()
int cy = 0;
lines = StrUtil::WordWrap(input, m_W, false, false, false, 2, inputPos, cy, cx);

// Vim visual selection: compute anchor screen coords + selection ordering
bool selActive = m_Model->GetVimModeLocked() && m_Model->GetVimVisualLocked();
int selStartY = 0, selStartX = 0, selEndY = 0, selEndX = 0;
if (selActive)
{
int ax = 0, ay = 0;
std::vector<int> dummyJunk; (void)dummyJunk;
int anchorPos = m_Model->GetVimVisualAnchorLocked();
int tcx = 0, tcy = 0;
StrUtil::WordWrap(input, m_W, false, false, false, 2, anchorPos, tcy, tcx);
ay = tcy; ax = tcx;
// order anchor vs cursor in reading order; selection is inclusive of both cells
if ((ay < cy) || ((ay == cy) && (ax <= cx)))
{
selStartY = ay; selStartX = ax; selEndY = cy; selEndX = cx;
}
else
{
selStartY = cy; selStartX = cx; selEndY = ay; selEndX = ax;
}
}

static int colorPair = UiColorConfig::GetColorPair("entry_color");
static int attribute = UiColorConfig::GetAttribute("entry_attr");

Expand All @@ -52,11 +74,29 @@ void UiEntryView::Draw()

for (int i = 0; i < m_H; ++i)
{
if ((i + yoffs) < (int)lines.size())
int lineIdx = i + yoffs;
if (lineIdx < (int)lines.size())
{
line = lines.at(i + yoffs).c_str();
line = lines.at(lineIdx).c_str();
line.erase(std::remove(line.begin(), line.end(), EMOJI_PAD), line.end());
mvwaddwstr(m_Win, i, 0, line.c_str());

// Overdraw the selected span on this row with reverse video
if (selActive && (lineIdx >= selStartY) && (lineIdx <= selEndY))
{
int len = (int)line.size();
int a = (lineIdx == selStartY) ? selStartX : 0;
int b = (lineIdx == selEndY) ? selEndX : (len - 1);
a = std::max(0, std::min(a, len - 1));
b = std::max(0, std::min(b, len - 1));
if (len > 0 && b >= a)
{
std::wstring seg = line.substr(a, b - a + 1);
wattron(m_Win, attribute | colorPair | A_REVERSE);
mvwaddnwstr(m_Win, i, a, seg.c_str(), seg.size());
wattroff(m_Win, A_REVERSE);
}
}
}
}

Expand Down
Loading