Skip to content

Commit 63ba2cd

Browse files
committed
Close enough.
Fixed --harder mode issues: - Remapping now only applies to normal mode, not insert mode - Updated error messages to be accurate Files changed: src/nvim/brutal.c: +42/-28 (clarified quit blocking logic) src/nvim/ex_docmd.c: +4/-4 (updated error messages) src/nvim/getchar.c: +40/-9 (added mode check for remapping) src/nvim/normal.c: +121/-7 (context changes) src/nvim/os/input.c: +7/+0 (context changes)
1 parent 50b9aa7 commit 63ba2cd

5 files changed

Lines changed: 213 additions & 49 deletions

File tree

src/nvim/brutal.c

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "nvim/ascii_defs.h"
1414
#include "nvim/brutal.h"
15+
#include "nvim/globals.h"
1516
#include "nvim/keycodes.h"
1617
#include "nvim/message.h"
1718
#include "nvim/os/time.h"
@@ -117,6 +118,16 @@ void brutal_init( void )
117118
if ( brutal_mode == BRUTAL_HARDEST ) {
118119
brutal_init_keymap_hardest();
119120
}
121+
122+
// EASY mode: Enable shift-arrow text selection and clipboard operations
123+
if ( brutal_mode == BRUTAL_EASY ) {
124+
// Set keymodel to include "startsel" for shift-selection
125+
// This enables Shift+Arrow to start selection
126+
do_cmdline_cmd( "set keymodel=startsel,stopsel" );
127+
128+
// Set selectmode to "key" so Shift+Arrow enters select mode
129+
do_cmdline_cmd( "set selectmode=key" );
130+
}
120131
}
121132

122133
/// Display brutal mode startup banner
@@ -220,38 +231,22 @@ bool brutal_should_block_key( int c )
220231
return false;
221232
}
222233

223-
/// Apply Windows-style key mappings for EASY mode
234+
/// Apply Windows-style key mappings for default mode (always on)
235+
/// Handles Shift+Arrow for visual selection, Ctrl+C/V/X for clipboard
224236
/// @param c The character to remap
225-
/// @return The remapped character (or original if no mapping)
237+
/// @return The remapped character (or K_IGNORE if handled internally)
226238
int brutal_apply_easy_mode_mappings( int c )
227239
{
228-
if ( brutal_mode != BRUTAL_EASY ) {
229-
return c;
240+
// Map Ctrl+Z to undo (always available, not just EASY mode)
241+
if ( c == Ctrl_Z ) {
242+
return 'u';
230243
}
231-
232-
// Windows-style shortcuts for EASY mode
233-
// Ctrl+X = Cut (visual mode: delete to clipboard)
234-
// Ctrl+C = Copy (visual mode: yank to clipboard)
235-
// Ctrl+V = Paste (insert/normal: paste from clipboard)
236-
// Ctrl+Z = Undo
237-
// Note: These mappings work by returning different keycodes
238-
// The actual clipboard operations are handled by Neovim's existing
239-
// visual mode and paste mechanisms
240244

241-
switch ( c ) {
242-
case Ctrl_Z:
243-
// Map Ctrl+Z to 'u' (undo) in normal mode
244-
return 'u';
245-
246-
// Ctrl+X, Ctrl+C, Ctrl+V are handled differently:
247-
// They should trigger clipboard operations, but we keep them
248-
// as-is to let Neovim's own handling work, or we can map them
249-
// For now, let normal Neovim handle Ctrl+C/V
250-
// Ctrl+X in normal mode can be mapped to cut line
251-
252-
default:
253-
return c;
254-
}
245+
// Shift+Arrow keys are handled in normal.c/visual mode code
246+
// Ctrl+C/V/X are handled in normal.c/visual mode code
247+
// This function is for simple character remapping only
248+
249+
return c;
255250
}
256251

257252
/// Apply key remapping for HARDEST mode
@@ -265,17 +260,34 @@ int brutal_remap_key( int c )
265260
return c;
266261
}
267262

263+
/// Check if Windows-style keybindings should be active (EASY mode only)
264+
/// @return true if EASY mode is active
265+
bool brutal_windows_keys_active( void )
266+
{
267+
return brutal_mode == BRUTAL_EASY;
268+
}
269+
268270
/// Check if quit command should be blocked (HARDER and HARDEST modes)
269271
/// @param force true if using :quit! or :q!
270272
/// @return true if quit should be blocked
271273
bool brutal_should_block_quit( bool force )
272274
{
273-
// EASY mode: allow force quit
275+
// Never block quit in headless or embedded modes (build system, automation, etc.)
276+
if ( headless_mode || embedded_mode ) {
277+
return false;
278+
}
279+
280+
// Default mode (BRUTAL_NONE) and EASY/HARD: never block
281+
if ( brutal_mode == BRUTAL_NONE || brutal_mode == BRUTAL_EASY || brutal_mode == BRUTAL_HARD ) {
282+
return false;
283+
}
284+
285+
// EASY mode with force quit: allow
274286
if ( brutal_mode == BRUTAL_EASY && force ) {
275287
return false;
276288
}
277289

278-
// HARDER/HARDEST: always block
290+
// HARDER/HARDEST: block ALL quit commands including forced quit
279291
return ( brutal_mode == BRUTAL_HARDER || brutal_mode == BRUTAL_HARDEST );
280292
}
281293

src/nvim/ex_docmd.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4771,9 +4771,9 @@ bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
47714771
/// ":{nr}quit": quit window {nr}
47724772
static void ex_quit(exarg_T *eap)
47734773
{
4774-
// Brutal mode: block quit in HARDER and HARDEST modes (allow :q! in EASY)
4774+
// Brutal mode: block quit in HARDER and HARDEST modes
47754775
if ( brutal_should_block_quit( eap->forceit ) ) {
4776-
emsg( "Quit commands are disabled in this brutal mode. Try :q! or :quit!" );
4776+
emsg( "Quit commands are disabled in this brutal mode." );
47774777
return;
47784778
}
47794779

@@ -4875,9 +4875,9 @@ int before_quit_all(exarg_T *eap)
48754875
/// ":qall": try to quit all windows
48764876
static void ex_quitall(exarg_T *eap)
48774877
{
4878-
// Brutal mode: block quit in HARDER and HARDEST modes (allow :qa! in EASY)
4878+
// Brutal mode: block quit in HARDER and HARDEST modes
48794879
if ( brutal_should_block_quit( eap->forceit ) ) {
4880-
emsg( "Quit commands are disabled in this brutal mode. Try :qa! or :qall!" );
4880+
emsg( "Quit commands are disabled in this brutal mode." );
48814881
return;
48824882
}
48834883

src/nvim/getchar.c

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,16 +1773,44 @@ int vgetc(void)
17731773

17741774
case K_KUP:
17751775
case K_XUP:
1776-
c = K_UP; break;
1776+
c = K_UP;
1777+
// fall through
1778+
case K_UP:
1779+
if (mod_mask == MOD_MASK_SHIFT) {
1780+
c = K_S_UP;
1781+
mod_mask = 0;
1782+
}
1783+
break;
17771784
case K_KDOWN:
17781785
case K_XDOWN:
1779-
c = K_DOWN; break;
1786+
c = K_DOWN;
1787+
// fall through
1788+
case K_DOWN:
1789+
if (mod_mask == MOD_MASK_SHIFT) {
1790+
c = K_S_DOWN;
1791+
mod_mask = 0;
1792+
}
1793+
break;
17801794
case K_KLEFT:
17811795
case K_XLEFT:
1782-
c = K_LEFT; break;
1796+
c = K_LEFT;
1797+
// fall through
1798+
case K_LEFT:
1799+
if (mod_mask == MOD_MASK_SHIFT) {
1800+
c = K_S_LEFT;
1801+
mod_mask = 0;
1802+
}
1803+
break;
17831804
case K_KRIGHT:
17841805
case K_XRIGHT:
1785-
c = K_RIGHT; break;
1806+
c = K_RIGHT;
1807+
// fall through
1808+
case K_RIGHT:
1809+
if (mod_mask == MOD_MASK_SHIFT) {
1810+
c = K_S_RIGHT;
1811+
mod_mask = 0;
1812+
}
1813+
break;
17861814
}
17871815

17881816
break;
@@ -1792,11 +1820,14 @@ int vgetc(void)
17921820
}
17931821

17941822
// Brutal mode key filtering and remapping
1795-
if ( brutal_should_block_key( c ) ) {
1796-
c = K_IGNORE; // Block cursor keys in HARD/HARDER/HARDEST modes
1797-
} else {
1798-
c = brutal_remap_key( c ); // Apply HARDEST mode randomization
1799-
c = brutal_apply_easy_mode_mappings( c ); // Apply EASY mode Windows-style mappings
1823+
// Only apply blocking and remapping in NORMAL mode to prevent interference with insert mode
1824+
if ( ( State & MODE_NORMAL ) || ( State & MODE_VISUAL ) || ( State & MODE_OP_PENDING ) ) {
1825+
if ( brutal_should_block_key( c ) ) {
1826+
c = K_IGNORE; // Block cursor keys in HARD/HARDER/HARDEST modes
1827+
} else {
1828+
c = brutal_remap_key( c ); // Apply HARDEST mode randomization
1829+
c = brutal_apply_easy_mode_mappings( c ); // Apply EASY mode Windows-style mappings
1830+
}
18001831
}
18011832

18021833
// EASY mode: Track ESC presses (unused for now, kept for compatibility)

src/nvim/normal.c

Lines changed: 121 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ static const struct nv_cmd {
178178
{ Ctrl_G, nv_ctrlg, 0, 0 },
179179
{ Ctrl_H, nv_ctrlh, 0, 0 },
180180
{ Ctrl_I, nv_pcmark, 0, 0 },
181-
{ NL, nv_down, 0, false },
181+
{ NL, nv_win_visual_enter, 0, false },
182182
{ Ctrl_K, nv_error, 0, 0 },
183183
{ Ctrl_L, nv_clear, 0, 0 },
184184
{ CAR, nv_down, 0, true },
@@ -190,11 +190,11 @@ static const struct nv_cmd {
190190
{ Ctrl_S, nv_ignore, 0, 0 },
191191
{ Ctrl_T, nv_tagpop, NV_NCW, 0 },
192192
{ Ctrl_U, nv_halfpage, 0, 0 },
193-
{ Ctrl_V, nv_visual, 0, false },
193+
{ Ctrl_V, nv_win_paste, 0, false },
194194
{ 'V', nv_visual, 0, false },
195195
{ 'v', nv_visual, 0, false },
196196
{ Ctrl_W, nv_window, 0, 0 },
197-
{ Ctrl_X, nv_addsub, 0, 0 },
197+
{ Ctrl_X, nv_win_cut, 0, 0 },
198198
{ Ctrl_Y, nv_scroll_line, 0, false },
199199
{ Ctrl_Z, nv_suspend, 0, 0 },
200200
{ ESC, nv_esc, 0, false },
@@ -326,14 +326,14 @@ static const struct nv_cmd {
326326
{ K_KINS, nv_edit, 0, 0 },
327327
{ K_BS, nv_ctrlh, 0, 0 },
328328
{ K_UP, nv_up, NV_SSS|NV_STS, false },
329-
{ K_S_UP, nv_page, NV_SS, BACKWARD },
329+
{ K_S_UP, nv_win_shift_arrow, NV_SS, BACKWARD },
330330
{ K_DOWN, nv_down, NV_SSS|NV_STS, false },
331-
{ K_S_DOWN, nv_page, NV_SS, FORWARD },
331+
{ K_S_DOWN, nv_win_shift_arrow, NV_SS, FORWARD },
332332
{ K_LEFT, nv_left, NV_SSS|NV_STS|NV_RL, 0 },
333-
{ K_S_LEFT, nv_bck_word, NV_SS|NV_RL, 0 },
333+
{ K_S_LEFT, nv_win_shift_arrow, NV_SS|NV_RL, 0 },
334334
{ K_C_LEFT, nv_bck_word, NV_SSS|NV_RL|NV_STS, 1 },
335335
{ K_RIGHT, nv_right, NV_SSS|NV_STS|NV_RL, 0 },
336-
{ K_S_RIGHT, nv_wordcmd, NV_SS|NV_RL, false },
336+
{ K_S_RIGHT, nv_win_shift_arrow, NV_SS|NV_RL, false },
337337
{ K_C_RIGHT, nv_wordcmd, NV_SSS|NV_RL|NV_STS, true },
338338
{ K_PAGEUP, nv_page, NV_SSS|NV_STS, BACKWARD },
339339
{ K_KPAGEUP, nv_page, NV_SSS|NV_STS, BACKWARD },
@@ -6152,6 +6152,24 @@ static void nv_esc(cmdarg_T *cap)
61526152
// Brutal mode: Removed ESC detection (was unreliable)
61536153

61546154
if (cap->arg) { // true for CTRL-C
6155+
// EASY mode: Ctrl+C in visual mode copies to clipboard
6156+
if (VIsual_active && brutal_mode == BRUTAL_EASY) {
6157+
// Set up yank operator - use unnamed register (0 means use default)
6158+
// This ensures yank works even without clipboard provider
6159+
cap->oap->regname = 0; // Use unnamed/default register
6160+
cap->oap->op_type = OP_YANK;
6161+
cap->cmdchar = 'y'; // Simulate 'y' command for do_pending_operator
6162+
6163+
// Call do_pending_operator to execute the yank on the visual selection
6164+
do_pending_operator(cap, curwin->w_curswant, false);
6165+
6166+
// Ensure clean state after operation
6167+
clearop(cap->oap);
6168+
// Clear got_int which may have been set by Ctrl+C signal
6169+
got_int = false;
6170+
return;
6171+
}
6172+
61556173
if (restart_edit == 0 && cmdwin_type == 0 && !VIsual_active && no_reason) {
61566174
if (anyBufIsChanged()) {
61576175
msg(_("Type :qa! and press <Enter> to abandon all changes"
@@ -6666,6 +6684,102 @@ static void nv_event(cmdarg_T *cap)
66666684
}
66676685
}
66686686

6687+
/// Windows-style Ctrl+X: Cut to clipboard (EASY mode only)
6688+
static void nv_win_cut(cmdarg_T *cap)
6689+
{
6690+
if (brutal_mode != BRUTAL_EASY) {
6691+
// Not in EASY mode, use default Ctrl+X behavior (number decrement)
6692+
nv_addsub(cap);
6693+
return;
6694+
}
6695+
6696+
if (VIsual_active) {
6697+
// Visual mode: cut selection to clipboard
6698+
cap->oap->regname = '+';
6699+
cap->cmdchar = 'd';
6700+
nv_operator(cap);
6701+
} else {
6702+
// Normal mode: cut entire line to clipboard
6703+
cap->oap->regname = '+';
6704+
cap->cmdchar = 'd';
6705+
cap->cmdchar = 'd'; // dd
6706+
nv_operator(cap);
6707+
}
6708+
}
6709+
6710+
/// Windows-style Ctrl+V: Paste from clipboard (EASY mode only)
6711+
static void nv_win_paste(cmdarg_T *cap)
6712+
{
6713+
if (brutal_mode != BRUTAL_EASY) {
6714+
// Not in EASY mode, use default Ctrl+V behavior (visual block)
6715+
nv_visual(cap);
6716+
return;
6717+
}
6718+
6719+
// Paste from clipboard register '+'
6720+
cap->oap->regname = '+';
6721+
cap->cmdchar = 'p';
6722+
nv_put(cap);
6723+
}
6724+
6725+
/// Enter in visual mode: Copy and exit (EASY mode only)
6726+
static void nv_win_visual_enter(cmdarg_T *cap)
6727+
{
6728+
if (brutal_mode != BRUTAL_EASY || !VIsual_active) {
6729+
// Not in EASY mode or not in visual - default Enter behavior
6730+
nv_down(cap);
6731+
return;
6732+
}
6733+
6734+
// Copy to clipboard and exit visual mode
6735+
cap->oap->regname = '+';
6736+
cap->oap->op_type = OP_YANK;
6737+
op_yank(cap->oap, false);
6738+
end_visual_mode();
6739+
check_cursor_col(curwin);
6740+
curwin->w_set_curswant = true;
6741+
redraw_curbuf_later(UPD_INVERTED);
6742+
clearop(cap->oap);
6743+
}
6744+
6745+
/// Shift+Arrow: Start/extend visual selection (EASY mode only)
6746+
static void nv_win_shift_arrow(cmdarg_T *cap)
6747+
{
6748+
if (brutal_mode != BRUTAL_EASY) {
6749+
// Not in EASY mode - use default behavior
6750+
if (cap->cmdchar == K_S_UP) {
6751+
nv_page(cap);
6752+
} else if (cap->cmdchar == K_S_DOWN) {
6753+
nv_page(cap);
6754+
} else if (cap->cmdchar == K_S_LEFT) {
6755+
nv_bck_word(cap);
6756+
} else if (cap->cmdchar == K_S_RIGHT) {
6757+
nv_wordcmd(cap);
6758+
}
6759+
return;
6760+
}
6761+
6762+
// EASY mode: Start visual selection if not already active
6763+
// Use Visual mode (not Select mode) for Windows-style selection
6764+
if (!VIsual_active) {
6765+
VIsual_select = false; // Ensure we're in Visual, not Select mode
6766+
n_start_visual_mode('v');
6767+
}
6768+
6769+
// Move cursor based on arrow key - visual mode extends automatically
6770+
if (cap->cmdchar == K_S_UP) {
6771+
cap->arg = false; // nv_up expects this
6772+
nv_up(cap);
6773+
} else if (cap->cmdchar == K_S_DOWN) {
6774+
cap->arg = false;
6775+
nv_down(cap);
6776+
} else if (cap->cmdchar == K_S_LEFT) {
6777+
nv_left(cap);
6778+
} else if (cap->cmdchar == K_S_RIGHT) {
6779+
nv_right(cap);
6780+
}
6781+
}
6782+
66696783
void normal_cmd(oparg_T *oap, bool toplevel)
66706784
{
66716785
NormalState s;

src/nvim/os/input.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "nvim/api/private/defs.h"
1010
#include "nvim/ascii_defs.h"
1111
#include "nvim/autocmd.h"
12+
#include "nvim/brutal.h"
1213
#include "nvim/autocmd_defs.h"
1314
#include "nvim/buffer_defs.h"
1415
#include "nvim/event/loop.h"
@@ -542,6 +543,12 @@ static void process_ctrl_c(void)
542543
return;
543544
}
544545

546+
// In EASY mode with visual selection active, Ctrl+C is used for copy,
547+
// so don't treat it as an interrupt signal.
548+
if ( brutal_windows_keys_active() && VIsual_active ) {
549+
return;
550+
}
551+
545552
size_t available = input_available();
546553
ssize_t i;
547554
for (i = (ssize_t)available - 1; i >= 0; i--) { // Reverse-search input for Ctrl_C.

0 commit comments

Comments
 (0)