From 259e6fa9ccb7354cb541cf77c64c0f8196cbe2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoffmann?= Date: Sat, 1 Aug 2026 18:01:00 +0200 Subject: [PATCH] fix(ruler)!: consistent width in last line and statusline Problem: Traditionally, the ruler in the last line is one cell shorter than in the statusline, leaving the last cell of the screen blank. According to code comments, this is in order to prevent unwanted scrolling on "some" (unspecified, but presumably ancient) terminals. Berkeley vi is more specific in its `vs_modeline` function: dumb terminals with hardware scroll, SunOS 4.1.1 and Ultrix 4.2 curses. (n)curses still has a similar limitation in `(w)addstr`, but apparently only for historical reasons. Maintaining the different widths leads to awkward inconsistencies when the ruler is configured with 'rulerformat', except for the special case where it contains a top-level `%=`. Shifting the ruler in the last line to the left would be a solution, but the empty cell at the end doesn't seem to be relevant anymore. Solution: extend the ruler in the last line all the way to the right edge of the screen, just like in the statusline. The exact same amount of place will be available to the rest of the UI as before. BREAKING CHANGE: - the default ruler width is now 18 cells - the last cell of the screen is no longer empty Closes #41076 --- runtime/doc/news.txt | 6 ++++++ runtime/doc/options.txt | 2 +- runtime/lua/vim/_meta/options.gen.lua | 2 +- src/nvim/drawscreen.c | 4 ++-- src/nvim/options.lua | 2 +- src/nvim/statusline.c | 6 ------ test/functional/api/window_spec.lua | 4 ++-- test/functional/editor/completion_spec.lua | 2 +- .../swapfile_preserve_recover_spec.lua | 2 +- test/functional/legacy/cmdline_spec.lua | 2 +- test/functional/legacy/excmd_spec.lua | 4 ++-- test/functional/legacy/listlbr_utf8_spec.lua | 8 ++++---- test/functional/legacy/normal_spec.lua | 2 +- test/functional/legacy/window_cmd_spec.lua | 2 +- test/functional/terminal/tui_spec.lua | 4 ++-- test/functional/terminal/window_spec.lua | 6 +++--- test/functional/ui/float_spec.lua | 12 +++++------ test/functional/ui/messages_spec.lua | 6 +++--- test/functional/ui/multibyte_spec.lua | 20 +++++++++---------- test/functional/ui/screen_basic_spec.lua | 2 +- test/functional/ui/statusline_spec.lua | 16 +++++---------- 21 files changed, 54 insertions(+), 60 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 0fd7f18e27..3afd74c260 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -163,6 +163,12 @@ TREESITTER • todo +UI + +• The bottom right cell of the screen is no longer left empty when the ruler + is shown. This was originally intended to prevent unwanted scrolling on + quirky terminals. + VIMSCRIPT • Removed: ctxget(), ctxpop(), ctxpush(), ctxset(), ctxsize(). Use diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 34a03b6fdb..47b36501db 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5212,7 +5212,7 @@ A jump table for the options with a short description can be found at |Q_op|. The format of this option is like that of 'statusline'. This option cannot be set in a modeline when 'modelineexpr' is off. - The default ruler width is 17 characters. To make the ruler 15 + The default ruler width is 18 characters. To make the ruler 15 characters wide, put "%15(" at the start and "%)" at the end. Example: >vim set rulerformat=%15(%c%V\ %p%%%) diff --git a/runtime/lua/vim/_meta/options.gen.lua b/runtime/lua/vim/_meta/options.gen.lua index 898d57edc2..25f2085916 100644 --- a/runtime/lua/vim/_meta/options.gen.lua +++ b/runtime/lua/vim/_meta/options.gen.lua @@ -5403,7 +5403,7 @@ vim.go.ru = vim.go.ruler --- The format of this option is like that of 'statusline'. --- This option cannot be set in a modeline when 'modelineexpr' is off. --- ---- The default ruler width is 17 characters. To make the ruler 15 +--- The default ruler width is 18 characters. To make the ruler 15 --- characters wide, put "%15(" at the start and "%)" at the end. --- Example: --- diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index a88790153c..0a0e8b6703 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1125,7 +1125,7 @@ static void recording_mode(int hl_id) msg_puts_hl(s, hl_id, false); } -#define COL_RULER 17 // columns needed by standard ruler +#define COL_RULER 18 // columns needed by standard ruler /// Compute columns for ruler and shown command. 'sc_col' is also used to /// decide what the maximum length of a message on the status line can be. @@ -1138,7 +1138,7 @@ void comp_col(void) sc_col = 0; ru_col = 0; if (p_ru) { - ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; + ru_col = (ru_wid ? ru_wid : COL_RULER); // no last status line, adjust sc_col if (!last_has_status) { sc_col = ru_col; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2b73f9c046..02920e5af6 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7312,7 +7312,7 @@ local options = { The format of this option is like that of 'statusline'. This option cannot be set in a modeline when 'modelineexpr' is off. - The default ruler width is 17 characters. To make the ruler 15 + The default ruler width is 18 characters. To make the ruler 15 characters wide, put "%15(" at the start and "%)" at the end. Example: >vim set rulerformat=%15(%c%V\ %p%%%) diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 01726a0a8f..2e27bfb0b7 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -338,7 +338,6 @@ static void win_redr_stl_expr(win_T *wp, bool draw_winbar, bool draw_ruler, bool if (!in_status_line) { row = Rows - 1; grid = grid_adjust(&msg_grid_adj, &row, &col); - maxwidth--; // writing in last column may cause scrolling fillchar = schar_from_ascii(' '); group = HLF_MSG; } @@ -557,14 +556,9 @@ void redraw_ruler(void) (int)virtcol + 1); // Add a "50%" if there is room for it. - // On the last line, don't print in the last column (scrolls the - // screen up on some terminals). char rel_pos[RULER_BUF_LEN]; int rel_poslen = get_rel_pos(wp, rel_pos, RULER_BUF_LEN); int n1 = bufferlen + vim_strsize(rel_pos); - if (wp->w_status_height == 0 && !is_stl_global && !ui_has(kUIMessages)) { - n1++; // can't use last char of screen - } int this_ru_col = ru_col - (Columns - width); // Never use more than half the window/screen width, leave the other half diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 8d8113dff3..cd6892d07b 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2337,7 +2337,7 @@ describe('API/win', function() screen:expect([[ │^ | ~ │~ |*4 - 0,0-1 All | + 0,0-1 All| {5:-- TERMINAL --} | ]]) screen:detach() @@ -2351,7 +2351,7 @@ describe('API/win', function() screen:expect([[ ^ │ | ~ │~ |*4 - 0,0-1 All | + 0,0-1 All| {5:-- TERMINAL --} | ]]) end) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 837453edca..86b5bca879 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1507,7 +1507,7 @@ describe('completion', function() {12:hello }{1: }| {4:hullo }{1: }| {4:heee }{1: }| - {5:-- INSERT --} 4,6 All | + {5:-- INSERT --} 4,6 All| ]]) end) diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 383fe672ef..f9064e4b74 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -429,7 +429,7 @@ describe('swapfile detection', function() screen:expect([[ ^ | {1:~ }|*16 - {19:W325: Ignoring swapfile from Nvim process }0,0-1 All | + {19:W325: Ignoring swapfile from Nvim process }0,0-1 All| ]]) eq(('\n' .. msg_expected):rep(3):sub(2), n.exec_capture('messages')) command('bwipe!') diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index cc8c31bb7c..a54cd50b84 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -386,7 +386,7 @@ describe('cmdline', function() api.nvim_set_option_value('rulerformat', '%!TestRulerFn()', {}) screen:expect([[ ^ | - 10,20 30% | + 10,20 30%| ]]) end) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index 5eaf495b63..fde106e53a 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -263,7 +263,7 @@ describe(':confirm command dialog', function() fooba^r | {1:~ }|*5 | - 1,6 All | + 1,6 All| ]]) eq('foo\n', read_file('Xconfirm_write_ro')) @@ -377,7 +377,7 @@ describe(':confirm command dialog', function() d | {1:~ }|*2 | - 1,1 All | + 1,1 All| ]]) eq('a\nb\nc\nd\n', read_file('Xwrite_partial')) os.remove('Xwrite_partial') diff --git a/test/functional/legacy/listlbr_utf8_spec.lua b/test/functional/legacy/listlbr_utf8_spec.lua index ef6187f13b..b2df33544b 100644 --- a/test/functional/legacy/listlbr_utf8_spec.lua +++ b/test/functional/legacy/listlbr_utf8_spec.lua @@ -249,7 +249,7 @@ describe('linebreak', function() {17:foo}^ │{1:~ }| xxxxxxxxxxxxxxxxxxxx│{1:~ }| {1:~ }│{1:~ }|*2 - {5:-- VISUAL BLOCK --} 2x4 2,4 All | + {5:-- VISUAL BLOCK --} 2x4 2,4 All| ]]) -- TAB as end char: 'linebreak' shouldn't break Visual block hl @@ -264,7 +264,7 @@ describe('linebreak', function() f{17:oo12345}bar | f^o{17:o }bar | {1:~ }|*2 - {5:-- VISUAL BLOCK --} 3x7 3,2 All | + {5:-- VISUAL BLOCK --} 3x7 3,2 All| ]]) feed(':setlocal linebreakgv') screen:expect_unchanged(true) @@ -281,7 +281,7 @@ describe('linebreak', function() f{17:oo123456}bar | f^o{17:o}bar | {1:~ }|*2 - {5:-- VISUAL BLOCK --} 3x8 3,2 All | + {5:-- VISUAL BLOCK --} 3x8 3,2 All| ]]) feed(':setlocal linebreakgv') screen:expect_unchanged(true) @@ -303,7 +303,7 @@ describe('linebreak', function() xx{17:xx}foo: {17:x}xxxxx | xx{17:xx}bar: ^xxxxxx | {1:~ }|*2 - {5:-- VISUAL BLOCK --} 3x8 3,5-10 All | + {5:-- VISUAL BLOCK --} 3x8 3,5-10 All| ]]) feed(':setlocal linebreakgv') screen:expect_unchanged(true) diff --git a/test/functional/legacy/normal_spec.lua b/test/functional/legacy/normal_spec.lua index 1f16bc7f4f..19aa76920a 100644 --- a/test/functional/legacy/normal_spec.lua +++ b/test/functional/legacy/normal_spec.lua @@ -158,7 +158,7 @@ describe('normal', function() ^4 | 5 | 6 | - 40 more lines 5,1 %8 | + 40 more lines 5,1 %8| ]]) end) end) diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index d4f714bd87..27e71a0db3 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -31,7 +31,7 @@ it('scrolling with laststatus=0 and :botright split', function() 98 | 99 | ^100 | - 100,1 Bot | + 100,1 Bot| ]]) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 263c78057a..34029f40fc 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -821,7 +821,7 @@ describe('TUI :restart', function() screen:expect([[ ^ | ~ |*4 - 0,0-1 All | + 0,0-1 All| {5:-- TERMINAL --} | ]]) @@ -833,7 +833,7 @@ describe('TUI :restart', function() 0002;;Cc;0;BN;;;;;N;START OF TEXT;;;; | 0003;;Cc;0;BN;;;;;N;END OF TEXT;;;; | 0004;;Cc;0;BN;;;;;N;END OF TRANSMISSION;;| - TRIGGERED: 1 1,1 Top | + TRIGGERED: 1 1,1 Top| {5:-- TERMINAL --} | ]]) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 752def3c57..0421fc0737 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -423,7 +423,7 @@ describe(':terminal window', function() cool line 8 | cool line 9 | cool line 10 | - {5:-- TERMINAL --} 6,1 Bot | + {5:-- TERMINAL --} 6,1 Bot| ]]) command('call nvim_win_set_cursor(0, [1, 0])') screen:expect_unchanged() @@ -436,7 +436,7 @@ describe(':terminal window', function() cool line 8 | cool line 9 | cool line 10 | - {5:-- TERMINAL --} 7,5 Bot | + {5:-- TERMINAL --} 7,5 Bot| ]]) -- Check topline correct after leaving terminal mode. -- The new cursor position is one column left of the terminal's actual cursor position. @@ -448,7 +448,7 @@ describe(':terminal window', function() cool line 8 | cool line 9 | cool line 10 | - 7,4 Bot | + 7,4 Bot| ]]) end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index ce59c22690..b57fa6f018 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -3578,7 +3578,7 @@ describe('float window', function() | {0:~ }|*5 ## grid 3 - 1,1 All | + 1,1 All| ## grid 4 {1:^aaa aab }| {1:abb acc }| @@ -3597,7 +3597,7 @@ describe('float window', function() {0:~ }{1:abb acc }{0: }| {0:~ }{2:~ }{0: }| {0:~ }|*3 - 1,1 All | + 1,1 All| ]], } end @@ -3613,7 +3613,7 @@ describe('float window', function() | {0:~ }|*5 ## grid 3 - 1,5 All | + 1,5 All| ## grid 4 {1:aaa ^aab }| {1:abb acc }| @@ -3632,7 +3632,7 @@ describe('float window', function() {0:~ }{1:abb acc }{0: }| {0:~ }{2:~ }{0: }| {0:~ }|*3 - 1,5 All | + 1,5 All| ]], } end @@ -3692,7 +3692,7 @@ describe('float window', function() | {0:~ }|*7 ## grid 3 - 0,0-1 All | + 0,0-1 All| ## grid 4 {1: }| {2:~ }|*2 @@ -3716,7 +3716,7 @@ describe('float window', function() {1: } {1:^ } | {2:~ }{0: }{2:~ }{0: }|*2 {0:~ }|*5 - 0,0-1 All | + 0,0-1 All| ]], } end diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 29119bda3f..604e7e0d36 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1894,7 +1894,7 @@ describe('ui/builtin messages', function() screen:expect([[ ^ | {1:~ }|*5 - 0-1 100% | + 0-1 100% | ]]) -- Ruler is cleared when it is no longer drawn. @@ -1909,14 +1909,14 @@ describe('ui/builtin messages', function() screen:expect([[ ^ | {1:~ }|*5 - 0,0-1 All | + 0,0-1 All| ]]) command('hi MsgArea guibg=#333333') screen:expect([[ ^ | {1:~ }|*5 - {101: 0,0-1 All }| + {101: 0,0-1 All}| ]]) end) diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua index 2f15be9d4b..5d265d724b 100644 --- a/test/functional/ui/multibyte_spec.lua +++ b/test/functional/ui/multibyte_spec.lua @@ -303,35 +303,35 @@ describe('multibyte rendering', function() screen:expect([[ ^🏳️‍⚧️ | {1:~ }|*4 - 1,1 All | + 1,1 All| ]]) feed('a word') screen:expect([[ 🏳️‍⚧️ wor^d | {1:~ }|*4 - 1,21-7 All | + 1,21-7 All| ]]) feed('0') screen:expect([[ ^🏳️‍⚧️ word | {1:~ }|*4 - 1,1 All | + 1,1 All| ]]) feed('l') screen:expect([[ 🏳️‍⚧️^ word | {1:~ }|*4 - 1,17-3 All | + 1,17-3 All| ]]) feed('h') screen:expect([[ ^🏳️‍⚧️ word | {1:~ }|*4 - 1,1 All | + 1,1 All| ]]) feed('o❤️ variant selected') @@ -339,7 +339,7 @@ describe('multibyte rendering', function() 🏳️‍⚧️ word | ❤️ variant selecte^d | {1:~ }|*3 - 2,23-19 All | + 2,23-19 All| ]]) feed('0') @@ -347,7 +347,7 @@ describe('multibyte rendering', function() 🏳️‍⚧️ word | ^❤️ variant selected | {1:~ }|*3 - 2,1 All | + 2,1 All| ]]) feed('l') @@ -355,7 +355,7 @@ describe('multibyte rendering', function() 🏳️‍⚧️ word | ❤️^ variant selected | {1:~ }|*3 - 2,7-3 All | + 2,7-3 All| ]]) feed('h') @@ -363,7 +363,7 @@ describe('multibyte rendering', function() 🏳️‍⚧️ word | ^❤️ variant selected | {1:~ }|*3 - 2,1 All | + 2,1 All| ]]) -- without selector: single width (note column 18 and not 19) @@ -373,7 +373,7 @@ describe('multibyte rendering', function() ❤️ variant selected | ❤ variant selecte^d | {1:~ }|*2 - 3,20-18 All | + 3,20-18 All| ]]) end) end) diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 194d1fb1c7..79726786e5 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -366,7 +366,7 @@ local function screen_tests(linegrid) 0123^456 | 789 | {1:~ }|*11 - 1,5 All | + 1,5 All| ]]) end) end) diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index c70015b78f..d386f8bf52 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -346,7 +346,7 @@ describe('global statusline', function() screen:expect([[ ^ | {1:~ }|*14 - 0,0-1 All | + 0,0-1 All| ]]) command('set laststatus=3') @@ -402,7 +402,7 @@ describe('global statusline', function() {2:< 0,0-1 All <-1 All <}│{1:~ }| │{1:~ }| {1:~ }│{1:~ }|*4 - 0,0-1 All | + 0,0-1 All| ]]) command('set laststatus=3') @@ -816,16 +816,10 @@ describe('statusline', function() {1:~}{15:^ }{1: }| {1:~ }|*4 {2:[No Name] 0,0-1 All}| - 0,0-1 All | - ]]) - command('set rulerformat=%17(%l,%c%V%=%P%)') - screen:expect([[ - | - {1:~}{15:^ }{1: }| - {1:~ }|*4 - {2:[No Name] 0,0-1 All}| - 0,0-1 All | + 0,0-1 All| ]]) + command('set rulerformat=%18(%l,%c%V%=%P%)') + screen:expect_unchanged() command('set rulerformat&') api.nvim_win_close(win, true) screen:expect([[