mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	fix(api): wrong return value with reverse range + overlap #32956
Problem:  When iterating in reverse with {start} > {end} in
          `nvim_buf_get_extmarks()`, marks that overlap {start} and are
          greater than {end} are included in the return value twice.
          Marks that overlap {end} and do not overlap {start} are not
          not included in the return value at all. Marks are not
          actually returned in a meaningful "traversal order".
Solution: Rather than actually iterating in reverse, (also possible but
          requires convoluted conditions and would require fetching
          overlapping marks for both the {start} and {end} position,
          while still ending up with non-traversal ordered marks),
          iterate normally and reverse the return value.
(cherry picked from commit 65170e8dad)
			
			
This commit is contained in:
		
				
					committed by
					
						
						github-actions[bot]
					
				
			
			
				
	
			
			
			
						parent
						
							3b0c88a537
						
					
				
				
					commit
					fb71d631a5
				
			@@ -2777,8 +2777,11 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
 | 
			
		||||
        vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
 | 
			
		||||
<
 | 
			
		||||
 | 
			
		||||
    If `end` is less than `start`, traversal works backwards. (Useful with
 | 
			
		||||
    `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
    If `end` is less than `start`, marks are returned in reverse order.
 | 
			
		||||
    (Useful with `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
 | 
			
		||||
    Note: For a reverse range, `limit` does not actually affect the traversed
 | 
			
		||||
    range, just how many marks are returned
 | 
			
		||||
 | 
			
		||||
    Note: when using extmark ranges (marks with a end_row/end_col position)
 | 
			
		||||
    the `overlap` option might be useful. Otherwise only the start position of
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								runtime/lua/vim/_meta/api.lua
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								runtime/lua/vim/_meta/api.lua
									
									
									
										generated
									
									
									
								
							@@ -369,8 +369,11 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
 | 
			
		||||
--- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
 | 
			
		||||
--- ```
 | 
			
		||||
---
 | 
			
		||||
--- If `end` is less than `start`, traversal works backwards. (Useful
 | 
			
		||||
--- with `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
--- If `end` is less than `start`, marks are returned in reverse order.
 | 
			
		||||
--- (Useful with `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
---
 | 
			
		||||
--- Note: For a reverse range, `limit` does not actually affect the traversed
 | 
			
		||||
--- range, just how many marks are returned
 | 
			
		||||
---
 | 
			
		||||
--- Note: when using extmark ranges (marks with a end_row/end_col position)
 | 
			
		||||
--- the `overlap` option might be useful. Otherwise only the start position
 | 
			
		||||
 
 | 
			
		||||
@@ -241,8 +241,11 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
 | 
			
		||||
/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// If `end` is less than `start`, traversal works backwards. (Useful
 | 
			
		||||
/// with `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
/// If `end` is less than `start`, marks are returned in reverse order.
 | 
			
		||||
/// (Useful with `limit`, to get the first marks prior to a given position.)
 | 
			
		||||
///
 | 
			
		||||
/// Note: For a reverse range, `limit` does not actually affect the traversed
 | 
			
		||||
/// range, just how many marks are returned
 | 
			
		||||
///
 | 
			
		||||
/// Note: when using extmark ranges (marks with a end_row/end_col position)
 | 
			
		||||
/// the `overlap` option might be useful. Otherwise only the start position
 | 
			
		||||
@@ -327,8 +330,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
 | 
			
		||||
    limit = INT64_MAX;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reverse = false;
 | 
			
		||||
 | 
			
		||||
  int l_row;
 | 
			
		||||
  colnr_T l_col;
 | 
			
		||||
  if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
 | 
			
		||||
@@ -341,17 +342,31 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
 | 
			
		||||
    return rv;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (l_row > u_row || (l_row == u_row && l_col > u_col)) {
 | 
			
		||||
    reverse = true;
 | 
			
		||||
  size_t rv_limit = (size_t)limit;
 | 
			
		||||
  bool reverse = l_row > u_row || (l_row == u_row && l_col > u_col);
 | 
			
		||||
  if (reverse) {
 | 
			
		||||
    limit = INT64_MAX;  // limit the return value instead
 | 
			
		||||
    int row = l_row;
 | 
			
		||||
    l_row = u_row;
 | 
			
		||||
    u_row = row;
 | 
			
		||||
    colnr_T col = l_col;
 | 
			
		||||
    l_col = u_col;
 | 
			
		||||
    u_col = col;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // note: ns_id=-1 allowed, represented as UINT32_MAX
 | 
			
		||||
  ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
 | 
			
		||||
                                       u_col, (int64_t)limit, reverse, type, opts->overlap);
 | 
			
		||||
                                       u_col, (int64_t)limit, type, opts->overlap);
 | 
			
		||||
 | 
			
		||||
  rv = arena_array(arena, kv_size(marks));
 | 
			
		||||
  for (size_t i = 0; i < kv_size(marks); i++) {
 | 
			
		||||
    ADD_C(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name, arena)));
 | 
			
		||||
  rv = arena_array(arena, MIN(kv_size(marks), rv_limit));
 | 
			
		||||
  if (reverse) {
 | 
			
		||||
    for (int i = (int)kv_size(marks) - 1; i >= 0 && kv_size(rv) < rv_limit; i--) {
 | 
			
		||||
      ADD_C(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name, arena)));
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    for (size_t i = 0; i < kv_size(marks); i++) {
 | 
			
		||||
      ADD_C(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name, arena)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  kv_destroy(marks);
 | 
			
		||||
 
 | 
			
		||||
@@ -259,11 +259,9 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
 | 
			
		||||
///
 | 
			
		||||
/// if upper_lnum or upper_col are negative the buffer
 | 
			
		||||
/// will be searched to the start, or end
 | 
			
		||||
/// reverse can be set to control the order of the array
 | 
			
		||||
/// amount = amount of marks to find or INT64_MAX for all
 | 
			
		||||
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
 | 
			
		||||
                             colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter,
 | 
			
		||||
                             bool overlap)
 | 
			
		||||
                             colnr_T u_col, int64_t amount, ExtmarkType type_filter, bool overlap)
 | 
			
		||||
{
 | 
			
		||||
  ExtmarkInfoArray array = KV_INITIAL_VALUE;
 | 
			
		||||
  MarkTreeIter itr[1];
 | 
			
		||||
@@ -281,29 +279,21 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co
 | 
			
		||||
  } else {
 | 
			
		||||
    // Find all the marks beginning with the start position
 | 
			
		||||
    marktree_itr_get_ext(buf->b_marktree, MTPos(l_row, l_col),
 | 
			
		||||
                         itr, reverse, false, NULL, NULL);
 | 
			
		||||
                         itr, false, false, NULL, NULL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int order = reverse ? -1 : 1;
 | 
			
		||||
  while ((int64_t)kv_size(array) < amount) {
 | 
			
		||||
    MTKey mark = marktree_itr_current(itr);
 | 
			
		||||
    if (mark.pos.row < 0
 | 
			
		||||
        || (mark.pos.row - u_row) * order > 0
 | 
			
		||||
        || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
 | 
			
		||||
        || (mark.pos.row > u_row)
 | 
			
		||||
        || (mark.pos.row == u_row && mark.pos.col > u_col)) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (mt_end(mark)) {
 | 
			
		||||
      goto next_mark;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
 | 
			
		||||
    push_mark(&array, ns_id, type_filter, mtpair_from(mark, end));
 | 
			
		||||
next_mark:
 | 
			
		||||
    if (reverse) {
 | 
			
		||||
      marktree_itr_prev(buf->b_marktree, itr);
 | 
			
		||||
    } else {
 | 
			
		||||
      marktree_itr_next(buf->b_marktree, itr);
 | 
			
		||||
    if (!mt_end(mark)) {
 | 
			
		||||
      MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
 | 
			
		||||
      push_mark(&array, ns_id, type_filter, mtpair_from(mark, end));
 | 
			
		||||
    }
 | 
			
		||||
    marktree_itr_next(buf->b_marktree, itr);
 | 
			
		||||
  }
 | 
			
		||||
  return array;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -331,30 +331,30 @@ describe('API/extmarks', function()
 | 
			
		||||
    rv = get_extmarks(ns, lower, upper)
 | 
			
		||||
    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
 | 
			
		||||
 | 
			
		||||
    -- prev with mark id
 | 
			
		||||
    -- reverse with mark id
 | 
			
		||||
    rv = get_extmarks(ns, marks[3], { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
 | 
			
		||||
    rv = get_extmarks(ns, marks[2], { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq({ { marks[2], positions[2][1], positions[2][2] } }, rv)
 | 
			
		||||
    -- prev with positional when mark exists at position
 | 
			
		||||
    -- reverse with positional when mark exists at position
 | 
			
		||||
    rv = get_extmarks(ns, positions[3], { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
 | 
			
		||||
    -- prev with positional index (no mark at position)
 | 
			
		||||
    -- reverse with positional index (no mark at position)
 | 
			
		||||
    rv = get_extmarks(ns, { positions[1][1], positions[1][2] + 1 }, { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
 | 
			
		||||
    -- prev with Extremity index
 | 
			
		||||
    -- reverse with Extremity index
 | 
			
		||||
    rv = get_extmarks(ns, { -1, -1 }, { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
 | 
			
		||||
 | 
			
		||||
    -- prevrange with mark id
 | 
			
		||||
    -- reverse with mark id
 | 
			
		||||
    rv = get_extmarks(ns, marks[3], marks[1])
 | 
			
		||||
    eq({ marks[3], positions[3][1], positions[3][2] }, rv[1])
 | 
			
		||||
    eq({ marks[2], positions[2][1], positions[2][2] }, rv[2])
 | 
			
		||||
    eq({ marks[1], positions[1][1], positions[1][2] }, rv[3])
 | 
			
		||||
    -- prevrange with limit
 | 
			
		||||
    -- reverse with limit
 | 
			
		||||
    rv = get_extmarks(ns, marks[3], marks[1], { limit = 2 })
 | 
			
		||||
    eq(2, #rv)
 | 
			
		||||
    -- prevrange with positional when mark exists at position
 | 
			
		||||
    -- reverse with positional when mark exists at position
 | 
			
		||||
    rv = get_extmarks(ns, positions[3], positions[1])
 | 
			
		||||
    eq({
 | 
			
		||||
      { marks[3], positions[3][1], positions[3][2] },
 | 
			
		||||
@@ -363,7 +363,7 @@ describe('API/extmarks', function()
 | 
			
		||||
    }, rv)
 | 
			
		||||
    rv = get_extmarks(ns, positions[2], positions[1])
 | 
			
		||||
    eq(2, #rv)
 | 
			
		||||
    -- prevrange with positional index (no mark at position)
 | 
			
		||||
    -- reverse with positional index (no mark at position)
 | 
			
		||||
    lower = { positions[2][1], positions[2][2] + 1 }
 | 
			
		||||
    upper = { positions[3][1], positions[3][2] + 1 }
 | 
			
		||||
    rv = get_extmarks(ns, upper, lower)
 | 
			
		||||
@@ -372,7 +372,7 @@ describe('API/extmarks', function()
 | 
			
		||||
    upper = { positions[3][1], positions[3][2] + 2 }
 | 
			
		||||
    rv = get_extmarks(ns, upper, lower)
 | 
			
		||||
    eq({}, rv)
 | 
			
		||||
    -- prevrange with extremity index
 | 
			
		||||
    -- reverse with extremity index
 | 
			
		||||
    lower = { 0, 0 }
 | 
			
		||||
    upper = { positions[2][1], positions[2][2] - 1 }
 | 
			
		||||
    rv = get_extmarks(ns, upper, lower)
 | 
			
		||||
@@ -428,6 +428,14 @@ describe('API/extmarks', function()
 | 
			
		||||
    set_extmark(ns, marks[3], 0, 2) -- check is inclusive
 | 
			
		||||
    local rv = get_extmarks(ns, { 2, 3 }, { 0, 2 })
 | 
			
		||||
    eq({ { marks[1], 2, 1 }, { marks[2], 1, 4 }, { marks[3], 0, 2 } }, rv)
 | 
			
		||||
    -- doesn't include paired marks whose start pos > lower bound twice
 | 
			
		||||
    -- and returns mark overlapping start pos but not end pos
 | 
			
		||||
    local m1 = set_extmark(ns, nil, 0, 0, { end_row = 1, end_col = 4 })
 | 
			
		||||
    local m2 = set_extmark(ns, nil, 0, 0, { end_row = 1, end_col = 2 })
 | 
			
		||||
    local m3 = set_extmark(ns, nil, 1, 0, { end_row = 1, end_col = 4 })
 | 
			
		||||
    local m4 = set_extmark(ns, nil, 1, 2, { end_row = 1, end_col = 4 })
 | 
			
		||||
    rv = get_extmarks(ns, { 1, 3 }, { 1, 2 }, { overlap = true })
 | 
			
		||||
    eq({ { m4, 1, 2 }, { m3, 1, 0 }, { m2, 0, 0 }, { m1, 0, 0 } }, rv)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  it('get_marks limit=0 returns nothing', function()
 | 
			
		||||
@@ -973,7 +981,7 @@ describe('API/extmarks', function()
 | 
			
		||||
    eq(1, #rv)
 | 
			
		||||
    rv = get_extmarks(ns2, { 0, 0 }, positions[2], { limit = 1 })
 | 
			
		||||
    eq(1, #rv)
 | 
			
		||||
    -- get_prev (limit set)
 | 
			
		||||
    -- reverse (limit set)
 | 
			
		||||
    rv = get_extmarks(ns, positions[1], { 0, 0 }, { limit = 1 })
 | 
			
		||||
    eq(1, #rv)
 | 
			
		||||
    rv = get_extmarks(ns2, positions[1], { 0, 0 }, { limit = 1 })
 | 
			
		||||
@@ -984,7 +992,7 @@ describe('API/extmarks', function()
 | 
			
		||||
    eq(2, #rv)
 | 
			
		||||
    rv = get_extmarks(ns2, positions[1], positions[2])
 | 
			
		||||
    eq(2, #rv)
 | 
			
		||||
    -- get_prev (no limit)
 | 
			
		||||
    -- reverse (no limit)
 | 
			
		||||
    rv = get_extmarks(ns, positions[2], positions[1])
 | 
			
		||||
    eq(2, #rv)
 | 
			
		||||
    rv = get_extmarks(ns2, positions[2], positions[1])
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user