Compare commits

...

4 Commits

Author SHA1 Message Date
Riley Bruins
9cc480dfdf
Merge 40527a4684 into 160cbd0ef4 2024-12-18 14:37:46 -05:00
luukvbaal
160cbd0ef4
test(cursor_spec): global highlight definitions (#31613) 2024-12-18 19:06:16 +00:00
Gregory Anders
3db3947b0e
fix(terminal): restore cursor from 'guicursor' on TermLeave (#31620)
Fixes: https://github.com/neovim/neovim/issues/31612
2024-12-18 11:41:05 -06:00
Riley Bruins
40527a4684 feat(api): add/del folds, get fold information 2024-12-11 23:44:35 -08:00
9 changed files with 305 additions and 99 deletions

View File

@ -2887,6 +2887,20 @@ nvim__ns_set({ns_id}, {opts}) *nvim__ns_set()*
==============================================================================
Window Functions *api-window*
*nvim_win_add_fold()*
nvim_win_add_fold({window}, {start}, {end}, {opts})
Add a fold to the window from {start} to {end}.
All row arguments are 0-indexed, inclusive.
Only supported for |fold-manual| and |fold-marker|.
Parameters: ~
• {window} Window handle, or 0 for current window.
• {start} Start row of fold.
• {end} End row of fold.
• {opts} Optional parameters. Reserved for future use.
nvim_win_call({window}, {fun}) *nvim_win_call()*
Calls a function with window as temporary current window.
@ -2917,6 +2931,21 @@ nvim_win_close({window}, {force}) *nvim_win_close()*
unwritten changes can be closed. The buffer will become
hidden, even if 'hidden' is not set.
*nvim_win_del_fold()*
nvim_win_del_fold({window}, {start}, {end}, {opts})
Delete a fold from the window from {start} to {end}.
All row arguments are 0-indexed, inclusive.
Only supported for |fold-manual| and |fold-marker|.
Parameters: ~
• {window} Window handle, or 0 for current window.
• {start} Start row of fold.
• {end} End row of fold.
• {opts} Optional parameters:
• recursive: Delete folds recursively
nvim_win_del_var({window}, {name}) *nvim_win_del_var()*
Removes a window-scoped (w:) variable
@ -2947,6 +2976,17 @@ nvim_win_get_cursor({window}) *nvim_win_get_cursor()*
See also: ~
• |getcurpos()|
nvim_win_get_folds({window}, {opts}) *nvim_win_get_folds()*
Get fold information from the window.
All row arguments are 0-indexed, inclusive.
Parameters: ~
• {window} Window handle, or 0 for current window.
• {opts} Optional parameters:
• start_row: get folds from this row
• end_row: get folds up to this row
nvim_win_get_height({window}) *nvim_win_get_height()*
Gets the window height

View File

@ -2341,6 +2341,18 @@ function vim.api.nvim_tabpage_set_var(tabpage, name, value) end
--- @param win integer Window handle, must already belong to {tabpage}
function vim.api.nvim_tabpage_set_win(tabpage, win) end
--- Add a fold to the window from {start} to {end}.
---
--- All row arguments are 0-indexed, inclusive.
---
--- Only supported for `fold-manual` and `fold-marker`.
---
--- @param window integer Window handle, or 0 for current window.
--- @param start integer Start row of fold.
--- @param end_ integer End row of fold.
--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use.
function vim.api.nvim_win_add_fold(window, start, end_, opts) end
--- Calls a function with window as temporary current window.
---
---
@ -2360,6 +2372,19 @@ function vim.api.nvim_win_call(window, fun) end
--- hidden, even if 'hidden' is not set.
function vim.api.nvim_win_close(window, force) end
--- Delete a fold from the window from {start} to {end}.
---
--- All row arguments are 0-indexed, inclusive.
---
--- Only supported for `fold-manual` and `fold-marker`.
---
--- @param window integer Window handle, or 0 for current window.
--- @param start integer Start row of fold.
--- @param end_ integer End row of fold.
--- @param opts vim.api.keyset.win_del_fold Optional parameters:
--- - recursive: Delete folds recursively
function vim.api.nvim_win_del_fold(window, start, end_, opts) end
--- Removes a window-scoped (w:) variable
---
--- @param window integer Window handle, or 0 for current window
@ -2392,6 +2417,17 @@ function vim.api.nvim_win_get_config(window) end
--- @return integer[] # (row, col) tuple
function vim.api.nvim_win_get_cursor(window) end
--- Get fold information from the window.
---
--- All row arguments are 0-indexed, inclusive.
---
--- @param window integer Window handle, or 0 for current window.
--- @param opts vim.api.keyset.empty Optional parameters:
--- - start_row: get folds from this row
--- - end_row: get folds up to this row
--- @return any[]
function vim.api.nvim_win_get_folds(window, opts) end
--- Gets the window height
---
--- @param window integer Window handle, or 0 for current window

View File

@ -308,6 +308,9 @@ error('Cannot require a meta file')
--- @field fixed? boolean
--- @field hide? boolean
--- @class vim.api.keyset.win_del_fold
--- @field recursive? boolean
--- @class vim.api.keyset.win_text_height
--- @field start_row? integer
--- @field end_row? integer

View File

@ -227,6 +227,11 @@ typedef struct {
Integer end_vcol;
} Dict(win_text_height);
typedef struct {
OptionalKeys is_set__win_del_fold_;
Boolean recursive;
} Dict(win_del_fold);
typedef struct {
OptionalKeys is_set__clear_autocmds_;
Buffer buffer;

View File

@ -4,6 +4,7 @@
#include <stdlib.h>
#include "nvim/api/keysets_defs.h"
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
@ -16,6 +17,7 @@
#include "nvim/errors.h"
#include "nvim/eval/window.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
@ -563,3 +565,114 @@ Dict nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *are
PUT_C(rv, "fill", INTEGER_OBJ(fill));
return rv;
}
/// Add a fold to the window from {start} to {end}.
///
/// All row arguments are 0-indexed, inclusive.
///
/// Only supported for |fold-manual| and |fold-marker|.
///
/// @param window Window handle, or 0 for current window.
/// @param start Start row of fold.
/// @param end End row of fold.
/// @param opts Optional parameters. Reserved for future use.
void nvim_win_add_fold(Window window, Integer start, Integer end, Dict(empty) *opts, Error *err)
FUNC_API_SINCE(11)
{
if (!foldManualAllowed(true)) {
return;
}
win_T *win = find_window_by_handle(window, err);
if (!win) {
return;
}
curwin->w_p_fen = true;
// Rows are zero-indexed.
pos_T start_pos = { start + 1, 1, 0 };
pos_T end_pos = { end + 1, 1, 0 };
foldCreate(win, start_pos, end_pos);
}
/// Delete a fold from the window from {start} to {end}.
///
/// All row arguments are 0-indexed, inclusive.
///
/// Only supported for |fold-manual| and |fold-marker|.
///
/// @param window Window handle, or 0 for current window.
/// @param start Start row of fold.
/// @param end End row of fold.
/// @param opts Optional parameters:
/// - recursive: Delete folds recursively
void nvim_win_del_fold(Window window, Integer start, Integer end, Dict(win_del_fold) *opts, Error *err)
FUNC_API_SINCE(11)
{
if (!foldManualAllowed(false)) {
return;
}
win_T *win = find_window_by_handle(window, err);
if (!win) {
return;
}
// Rows are zero-indexed.
deleteFold(win, start + 1, end + 1, opts->recursive, false);
}
/// Get fold information from the window.
///
/// All row arguments are 0-indexed, inclusive.
///
/// @param window Window handle, or 0 for current window.
/// @param opts Optional parameters:
/// - start_row: get folds from this row
/// - end_row: get folds up to this row
Array nvim_win_get_folds(Window window, Dict(empty) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(11)
{
win_T *win = find_window_by_handle(window, err);
if (!win) {
goto cleanup;
}
garray_T *gap = &win->w_folds;
return folds_to_fold_dict(gap, arena, win, 0);
cleanup:
return (Array)ARRAY_DICT_INIT;
}
static Array folds_to_fold_dict(garray_T *folds, Arena *arena, win_T *wp, int parent_start) {
fold_T *fp = (fold_T *)folds->ga_data;
int fold_count = folds->ga_len;
Array rv = arena_array(arena, fold_count);
for (int i = 0; i < fold_count; i++) {
fold_T fold = fp[i];
// Nested fold positions are relative to the parent
int top = fold.fd_top + parent_start;
Dict fold_dict = arena_dict(arena, 4);
char *state;
switch (fold.fd_flags) {
case FD_CLOSED:
state = "closed";
break;
case FD_OPEN:
state = "open";
break;
case FD_LEVEL:
// BUG: Still does not update for e.g. open nested folds after "zM"
state = foldLevelWin(wp, top) >= wp->w_p_fdl ? "closed" : "open";
break;
}
PUT_C(fold_dict, "state", CSTR_TO_ARENA_OBJ(arena, state));
PUT_C(fold_dict, "start_row", INTEGER_OBJ(top - 1));
PUT_C(fold_dict, "end_row", INTEGER_OBJ(top + fold.fd_len - 2));
Array inner_folds = folds_to_fold_dict(&fold.fd_nested, arena, wp, top);
PUT_C(fold_dict, "children", ARRAY_OBJ(inner_folds));
ADD_C(rv, DICT_OBJ(fold_dict));
}
return rv;
}

View File

@ -55,49 +55,6 @@
#include "nvim/undo.h"
#include "nvim/vim_defs.h"
// local declarations. {{{1
// typedef fold_T {{{2
// The toplevel folds for each window are stored in the w_folds growarray.
// Each toplevel fold can contain an array of second level folds in the
// fd_nested growarray.
// The info stored in both growarrays is the same: An array of fold_T.
typedef struct {
linenr_T fd_top; // first line of fold; for nested fold
// relative to parent
linenr_T fd_len; // number of lines in the fold
garray_T fd_nested; // array of nested folds
char fd_flags; // see below
TriState fd_small; // kTrue, kFalse, or kNone: fold smaller than
// 'foldminlines'; kNone applies to nested
// folds too
} fold_T;
enum {
FD_OPEN = 0, // fold is open (nested ones can be closed)
FD_CLOSED = 1, // fold is closed
FD_LEVEL = 2, // depends on 'foldlevel' (nested folds too)
};
#define MAX_LEVEL 20 // maximum fold depth
// Define "fline_T", passed to get fold level for a line. {{{2
typedef struct {
win_T *wp; // window
linenr_T lnum; // current line number
linenr_T off; // offset between lnum and real line number
linenr_T lnum_save; // line nr used by foldUpdateIEMSRecurse()
int lvl; // current level (-1 for undefined)
int lvl_next; // level used for next line
int start; // number of folds that are forced to start at
// this line.
int end; // level of fold that is forced to end below
// this line
int had_end; // level of fold that is forced to end above
// this line (copy of "end" of prev. line)
} fline_T;
// Flag is set when redrawing is needed.
static bool fold_changed;
@ -1086,7 +1043,7 @@ static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
// foldLevelWin() {{{2
/// @return fold level at line number "lnum" in window "wp".
static int foldLevelWin(win_T *wp, linenr_T lnum)
int foldLevelWin(win_T *wp, linenr_T lnum)
{
fold_T *fp;
linenr_T lnum_rel = lnum;

View File

@ -12,6 +12,51 @@
EXTERN int disable_fold_update INIT( = 0);
// local declarations. {{{1
// typedef fold_T {{{2
// The toplevel folds for each window are stored in the w_folds growarray.
// Each toplevel fold can contain an array of second level folds in the
// fd_nested growarray.
// The info stored in both growarrays is the same: An array of fold_T.
typedef struct {
linenr_T fd_top; // first line of fold; for nested fold
// relative to parent
linenr_T fd_len; // number of lines in the fold
garray_T fd_nested; // array of nested folds
char fd_flags; // see below
TriState fd_small; // kTrue, kFalse, or kNone: fold smaller than
// 'foldminlines'; kNone applies to nested
// folds too
} fold_T;
enum {
FD_OPEN = 0, // fold is open (nested ones can be closed)
FD_CLOSED = 1, // fold is closed
FD_LEVEL = 2, // depends on 'foldlevel' (nested folds too)
};
#define MAX_LEVEL 20 // maximum fold depth
// Define "fline_T", passed to get fold level for a line. {{{2
typedef struct {
win_T *wp; // window
linenr_T lnum; // current line number
linenr_T off; // offset between lnum and real line number
linenr_T lnum_save; // line nr used by foldUpdateIEMSRecurse()
int lvl; // current level (-1 for undefined)
int lvl_next; // level used for next line
int start; // number of folds that are forced to start at
// this line.
int end; // level of fold that is forced to end below
// this line
int had_end; // level of fold that is forced to end above
// this line (copy of "end" of prev. line)
} fline_T;
int foldLevelWin(win_T *wp, linenr_T lnum);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fold.h.generated.h"
#endif

View File

@ -641,9 +641,6 @@ bool terminal_enter(void)
curwin->w_p_so = 0;
curwin->w_p_siso = 0;
// Save the existing cursor entry since it may be modified by the application
cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM];
// Update the cursor shape table and flush changes to the UI
s->term->pending.cursor = true;
refresh_cursor(s->term);
@ -674,8 +671,8 @@ bool terminal_enter(void)
RedrawingDisabled = s->save_rd;
apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
shape_table[SHAPE_IDX_TERM] = save_cursorentry;
ui_mode_info_set();
// Restore the terminal cursor to what is set in 'guicursor'
(void)parse_shape_opt(SHAPE_CURSOR);
if (save_curwin == curwin->handle) { // Else: window was closed.
curwin->w_p_cul = save_w_p_cul;

View File

@ -15,9 +15,20 @@ local skip = t.skip
describe(':terminal cursor', function()
local screen
local terminal_mode_idx ---@type number
before_each(function()
clear()
screen = tt.setup_screen()
if terminal_mode_idx == nil then
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
terminal_mode_idx = i
end
end
assert(terminal_mode_idx)
end
end)
it('moves the screen cursor when focused', function()
@ -143,13 +154,6 @@ describe(':terminal cursor', function()
it('can be modified by application #3681', function()
skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
local states = {
[1] = { blink = true, shape = 'block' },
@ -171,13 +175,13 @@ describe(':terminal cursor', function()
]],
condition = function()
if v.blink then
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
else
eq(0, screen._mode_info[idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff)
eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
end
eq(v.shape, screen._mode_info[idx].cursor_shape)
eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
end
@ -191,20 +195,13 @@ describe(':terminal cursor', function()
]])
-- Cursor returns to default on TermLeave
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end)
it('can be modified per terminal', function()
skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
-- Set cursor to vertical bar with blink
tt.feed_csi('5 q')
@ -216,9 +213,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} |
]],
condition = function()
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
@ -231,9 +228,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} |
]],
condition = function()
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
@ -256,9 +253,9 @@ describe(':terminal cursor', function()
]],
condition = function()
-- New terminal, cursor resets to defaults
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
@ -275,9 +272,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} |
]],
condition = function()
eq(0, screen._mode_info[idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff)
eq('horizontal', screen._mode_info[idx].cursor_shape)
eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
@ -294,9 +291,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} |
]],
condition = function()
eq(500, screen._mode_info[idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape)
eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end,
})
end)
@ -326,6 +323,32 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} |
]])
end)
it('preserves guicursor value on TermLeave #31612', function()
eq(3, screen._mode_info[terminal_mode_idx].hl_id)
-- Change 'guicursor' while terminal mode is active
command('set guicursor+=t:Error')
local error_hl_id = call('hlID', 'Error')
screen:expect({
condition = function()
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end,
})
-- Exit terminal mode
feed([[<C-\><C-N>]])
screen:expect([[
tty ready |
^ |
|*5
]])
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end)
end)
describe('buffer cursor position is correct in terminal without number column', function()
@ -350,12 +373,6 @@ describe('buffer cursor position is correct in terminal without number column',
}, {
cols = 70,
})
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
})
-- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end
@ -667,13 +684,6 @@ describe('buffer cursor position is correct in terminal with number column', fun
}, {
cols = 70,
})
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
[7] = { foreground = 130 },
})
-- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end