neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

extmark_spec.lua (73786B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local request = n.request
      6 local eq = t.eq
      7 local ok = t.ok
      8 local pcall_err = t.pcall_err
      9 local insert = n.insert
     10 local feed = n.feed
     11 local clear = n.clear
     12 local command = n.command
     13 local exec = n.exec
     14 local exec_lua = n.exec_lua
     15 local api = n.api
     16 local fn = n.fn
     17 local assert_alive = n.assert_alive
     18 
     19 local function expect(contents)
     20  return eq(contents, n.curbuf_contents())
     21 end
     22 
     23 local function set_extmark(ns_id, id, line, col, opts)
     24  if opts == nil then
     25    opts = {}
     26  end
     27  if id ~= nil and id ~= 0 then
     28    opts.id = id
     29  end
     30  return api.nvim_buf_set_extmark(0, ns_id, line, col, opts)
     31 end
     32 
     33 local function get_extmarks(ns_id, start, end_, opts)
     34  if opts == nil then
     35    opts = {}
     36  end
     37  return api.nvim_buf_get_extmarks(0, ns_id, start, end_, opts)
     38 end
     39 
     40 local function get_extmark_by_id(ns_id, id, opts)
     41  if opts == nil then
     42    opts = {}
     43  end
     44  return api.nvim_buf_get_extmark_by_id(0, ns_id, id, opts)
     45 end
     46 
     47 local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
     48  local rv = get_extmark_by_id(ns, mark)
     49  eq({ er, ec }, rv)
     50  feed('u')
     51  rv = get_extmark_by_id(ns, mark)
     52  eq({ sr, sc }, rv)
     53  feed('<c-r>')
     54  rv = get_extmark_by_id(ns, mark)
     55  eq({ er, ec }, rv)
     56 end
     57 
     58 local function batch_set(ns_id, positions)
     59  local ids = {}
     60  for _, pos in ipairs(positions) do
     61    table.insert(ids, set_extmark(ns_id, 0, pos[1], pos[2]))
     62  end
     63  return ids
     64 end
     65 
     66 local function batch_check(ns_id, ids, positions)
     67  local actual, expected = {}, {}
     68  for i, id in ipairs(ids) do
     69    expected[id] = positions[i]
     70  end
     71  for _, mark in pairs(get_extmarks(ns_id, 0, -1, {})) do
     72    actual[mark[1]] = { mark[2], mark[3] }
     73  end
     74  eq(expected, actual)
     75 end
     76 
     77 local function batch_check_undo_redo(ns_id, ids, before, after)
     78  batch_check(ns_id, ids, after)
     79  feed('u')
     80  batch_check(ns_id, ids, before)
     81  feed('<c-r>')
     82  batch_check(ns_id, ids, after)
     83 end
     84 
     85 describe('API/extmarks', function()
     86  local screen
     87  local marks, positions, init_text, row, col
     88  local ns, ns2 ---@type integer, integer
     89 
     90  before_each(function()
     91    -- Initialize some namespaces and insert 12345 into a buffer
     92    marks = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }
     93    positions = { { 0, 0 }, { 0, 2 }, { 0, 3 } }
     94 
     95    init_text = '12345'
     96    row = 0
     97    col = 2
     98 
     99    clear()
    100 
    101    insert(init_text)
    102    ns = request('nvim_create_namespace', 'my-fancy-plugin')
    103    ns2 = request('nvim_create_namespace', 'my-fancy-plugin2')
    104  end)
    105 
    106  it('validation', function()
    107    eq(
    108      "Invalid 'end_col': expected Integer, got Array",
    109      pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = {}, end_row = 1 })
    110    )
    111    eq(
    112      "Invalid 'end_row': expected Integer, got Array",
    113      pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = {} })
    114    )
    115    eq(
    116      "Invalid 'virt_text_pos': expected String, got Integer",
    117      pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 0 })
    118    )
    119    eq(
    120      "Invalid 'virt_text_pos': 'foo'",
    121      pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 'foo' })
    122    )
    123    eq(
    124      "Invalid 'hl_mode': expected String, got Integer",
    125      pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 0 })
    126    )
    127    eq("Invalid 'hl_mode': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 'foo' }))
    128    eq(
    129      "Invalid 'virt_lines_overflow': 'foo'",
    130      pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_lines_overflow = 'foo' })
    131    )
    132    eq(
    133      "Invalid 'id': expected Integer, got Array",
    134      pcall_err(set_extmark, ns, {}, 0, 0, { end_col = 1, end_row = 1 })
    135    )
    136    eq(
    137      'Invalid mark position: expected 2 Integer items',
    138      pcall_err(get_extmarks, ns, {}, { -1, -1 })
    139    )
    140    eq(
    141      'Invalid mark position: expected mark id Integer or 2-item Array',
    142      pcall_err(get_extmarks, ns, true, { -1, -1 })
    143    )
    144    -- No memory leak with virt_text, virt_lines, sign_text
    145    eq(
    146      'right_gravity is not a boolean',
    147      pcall_err(set_extmark, ns, marks[2], 0, 0, {
    148        virt_text = { { 'foo', 'Normal' } },
    149        virt_lines = { { { 'bar', 'Normal' } } },
    150        sign_text = 'a',
    151        right_gravity = 'baz',
    152      })
    153    )
    154  end)
    155 
    156  it('can end extranges past final newline using end_col = 0', function()
    157    set_extmark(ns, marks[1], 0, 0, {
    158      end_col = 0,
    159      end_row = 1,
    160    })
    161    eq(
    162      "Invalid 'end_col': out of range",
    163      pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 })
    164    )
    165  end)
    166 
    167  it('can end extranges past final newline when strict mode is false', function()
    168    set_extmark(ns, marks[1], 0, 0, {
    169      end_col = 1,
    170      end_row = 1,
    171      strict = false,
    172    })
    173  end)
    174 
    175  it('can end extranges past final column when strict mode is false', function()
    176    set_extmark(ns, marks[1], 0, 0, {
    177      end_col = 6,
    178      end_row = 0,
    179      strict = false,
    180    })
    181  end)
    182 
    183  it('adds, updates  and deletes marks', function()
    184    local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
    185    eq(marks[1], rv)
    186    rv = get_extmark_by_id(ns, marks[1])
    187    eq({ positions[1][1], positions[1][2] }, rv)
    188    -- Test adding a second mark on same row works
    189    rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2])
    190    eq(marks[2], rv)
    191 
    192    -- Test an update, (same pos)
    193    rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
    194    eq(marks[1], rv)
    195    rv = get_extmark_by_id(ns, marks[2])
    196    eq({ positions[2][1], positions[2][2] }, rv)
    197    -- Test an update, (new pos)
    198    row = positions[1][1]
    199    col = positions[1][2] + 1
    200    rv = set_extmark(ns, marks[1], row, col)
    201    eq(marks[1], rv)
    202    rv = get_extmark_by_id(ns, marks[1])
    203    eq({ row, col }, rv)
    204 
    205    -- remove the test marks
    206    eq(true, api.nvim_buf_del_extmark(0, ns, marks[1]))
    207    eq(false, api.nvim_buf_del_extmark(0, ns, marks[1]))
    208    eq(true, api.nvim_buf_del_extmark(0, ns, marks[2]))
    209    eq(false, api.nvim_buf_del_extmark(0, ns, marks[3]))
    210    eq(false, api.nvim_buf_del_extmark(0, ns, 1000))
    211  end)
    212 
    213  it('can clear a specific namespace range', function()
    214    set_extmark(ns, 1, 0, 1)
    215    set_extmark(ns2, 1, 0, 1)
    216    -- force a new undo buffer
    217    feed('o<esc>')
    218    api.nvim_buf_clear_namespace(0, ns2, 0, -1)
    219    eq({ { 1, 0, 1 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    220    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    221    feed('u')
    222    eq({ { 1, 0, 1 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    223    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    224    feed('<c-r>')
    225    eq({ { 1, 0, 1 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    226    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    227  end)
    228 
    229  it('can clear a namespace range using 0,-1', function()
    230    set_extmark(ns, 1, 0, 1)
    231    set_extmark(ns2, 1, 0, 1)
    232    -- force a new undo buffer
    233    feed('o<esc>')
    234    api.nvim_buf_clear_namespace(0, -1, 0, -1)
    235    eq({}, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    236    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    237    feed('u')
    238    eq({}, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    239    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    240    feed('<c-r>')
    241    eq({}, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    242    eq({}, get_extmarks(ns2, { 0, 0 }, { -1, -1 }))
    243  end)
    244 
    245  it('can undo with extmarks (#25147)', function()
    246    feed('itest<esc>')
    247    set_extmark(ns, 1, 0, 0)
    248    set_extmark(ns, 2, 1, 0)
    249    eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    250    feed('dd')
    251    eq({ { 1, 1, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    252    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    253    eq({}, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    254    set_extmark(ns, 1, 0, 0, { right_gravity = false })
    255    set_extmark(ns, 2, 1, 0, { right_gravity = false })
    256    eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    257    feed('u')
    258    eq({ { 1, 0, 0 }, { 2, 0, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 }))
    259    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    260  end)
    261 
    262  it('querying for information and ranges', function()
    263    --marks = {1, 2, 3}
    264    --positions = {{0, 0,}, {0, 2}, {0, 3}}
    265    -- add some more marks
    266    for i, m in ipairs(marks) do
    267      if positions[i] ~= nil then
    268        local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
    269        eq(m, rv)
    270      end
    271    end
    272 
    273    -- {0, 0} and {-1, -1} work as extreme values
    274    eq({ { 1, 0, 0 } }, get_extmarks(ns, { 0, 0 }, { 0, 0 }))
    275    eq({}, get_extmarks(ns, { -1, -1 }, { -1, -1 }))
    276    local rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    277    for i, m in ipairs(marks) do
    278      if positions[i] ~= nil then
    279        eq({ m, positions[i][1], positions[i][2] }, rv[i])
    280      end
    281    end
    282 
    283    -- 0 and -1 works as short hand extreme values
    284    eq({ { 1, 0, 0 } }, get_extmarks(ns, 0, 0))
    285    eq({}, get_extmarks(ns, -1, -1))
    286    rv = get_extmarks(ns, 0, -1)
    287    for i, m in ipairs(marks) do
    288      if positions[i] ~= nil then
    289        eq({ m, positions[i][1], positions[i][2] }, rv[i])
    290      end
    291    end
    292 
    293    -- next with mark id
    294    rv = get_extmarks(ns, marks[1], { -1, -1 }, { limit = 1 })
    295    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
    296    rv = get_extmarks(ns, marks[2], { -1, -1 }, { limit = 1 })
    297    eq({ { marks[2], positions[2][1], positions[2][2] } }, rv)
    298    -- next with positional when mark exists at position
    299    rv = get_extmarks(ns, positions[1], { -1, -1 }, { limit = 1 })
    300    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
    301    -- next with positional index (no mark at position)
    302    rv = get_extmarks(ns, { positions[1][1], positions[1][2] + 1 }, { -1, -1 }, { limit = 1 })
    303    eq({ { marks[2], positions[2][1], positions[2][2] } }, rv)
    304    -- next with Extremity index
    305    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 1 })
    306    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
    307 
    308    -- nextrange with mark id
    309    rv = get_extmarks(ns, marks[1], marks[3])
    310    eq({ marks[1], positions[1][1], positions[1][2] }, rv[1])
    311    eq({ marks[2], positions[2][1], positions[2][2] }, rv[2])
    312    -- nextrange with `limit`
    313    rv = get_extmarks(ns, marks[1], marks[3], { limit = 2 })
    314    eq(2, #rv)
    315    -- nextrange with positional when mark exists at position
    316    rv = get_extmarks(ns, positions[1], positions[3])
    317    eq({ marks[1], positions[1][1], positions[1][2] }, rv[1])
    318    eq({ marks[2], positions[2][1], positions[2][2] }, rv[2])
    319    rv = get_extmarks(ns, positions[2], positions[3])
    320    eq(2, #rv)
    321    -- nextrange with positional index (no mark at position)
    322    local lower = { positions[1][1], positions[2][2] - 1 }
    323    local upper = { positions[2][1], positions[3][2] - 1 }
    324    rv = get_extmarks(ns, lower, upper)
    325    eq({ { marks[2], positions[2][1], positions[2][2] } }, rv)
    326    lower = { positions[3][1], positions[3][2] + 1 }
    327    upper = { positions[3][1], positions[3][2] + 2 }
    328    rv = get_extmarks(ns, lower, upper)
    329    eq({}, rv)
    330    -- nextrange with extremity index
    331    lower = { positions[2][1], positions[2][2] + 1 }
    332    upper = { -1, -1 }
    333    rv = get_extmarks(ns, lower, upper)
    334    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
    335 
    336    -- reverse with mark id
    337    rv = get_extmarks(ns, marks[3], { 0, 0 }, { limit = 1 })
    338    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
    339    rv = get_extmarks(ns, marks[2], { 0, 0 }, { limit = 1 })
    340    eq({ { marks[2], positions[2][1], positions[2][2] } }, rv)
    341    -- reverse with positional when mark exists at position
    342    rv = get_extmarks(ns, positions[3], { 0, 0 }, { limit = 1 })
    343    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
    344    -- reverse with positional index (no mark at position)
    345    rv = get_extmarks(ns, { positions[1][1], positions[1][2] + 1 }, { 0, 0 }, { limit = 1 })
    346    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
    347    -- reverse with Extremity index
    348    rv = get_extmarks(ns, { -1, -1 }, { 0, 0 }, { limit = 1 })
    349    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
    350 
    351    -- reverse with mark id
    352    rv = get_extmarks(ns, marks[3], marks[1])
    353    eq({ marks[3], positions[3][1], positions[3][2] }, rv[1])
    354    eq({ marks[2], positions[2][1], positions[2][2] }, rv[2])
    355    eq({ marks[1], positions[1][1], positions[1][2] }, rv[3])
    356    -- reverse with limit
    357    rv = get_extmarks(ns, marks[3], marks[1], { limit = 2 })
    358    eq(2, #rv)
    359    -- reverse with positional when mark exists at position
    360    rv = get_extmarks(ns, positions[3], positions[1])
    361    eq({
    362      { marks[3], positions[3][1], positions[3][2] },
    363      { marks[2], positions[2][1], positions[2][2] },
    364      { marks[1], positions[1][1], positions[1][2] },
    365    }, rv)
    366    rv = get_extmarks(ns, positions[2], positions[1])
    367    eq(2, #rv)
    368    -- reverse with positional index (no mark at position)
    369    lower = { positions[2][1], positions[2][2] + 1 }
    370    upper = { positions[3][1], positions[3][2] + 1 }
    371    rv = get_extmarks(ns, upper, lower)
    372    eq({ { marks[3], positions[3][1], positions[3][2] } }, rv)
    373    lower = { positions[3][1], positions[3][2] + 1 }
    374    upper = { positions[3][1], positions[3][2] + 2 }
    375    rv = get_extmarks(ns, upper, lower)
    376    eq({}, rv)
    377    -- reverse with extremity index
    378    lower = { 0, 0 }
    379    upper = { positions[2][1], positions[2][2] - 1 }
    380    rv = get_extmarks(ns, upper, lower)
    381    eq({ { marks[1], positions[1][1], positions[1][2] } }, rv)
    382  end)
    383 
    384  it('querying for information with limit', function()
    385    -- add some more marks
    386    for i, m in ipairs(marks) do
    387      if positions[i] ~= nil then
    388        local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
    389        eq(m, rv)
    390      end
    391    end
    392 
    393    local rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 1 })
    394    eq(1, #rv)
    395    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 2 })
    396    eq(2, #rv)
    397    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 3 })
    398    eq(3, #rv)
    399 
    400    -- now in reverse
    401    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 1 })
    402    eq(1, #rv)
    403    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 2 })
    404    eq(2, #rv)
    405    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 }, { limit = 3 })
    406    eq(3, #rv)
    407  end)
    408 
    409  it('get_marks works when mark col > upper col', function()
    410    feed('A<cr>12345<esc>')
    411    feed('A<cr>12345<esc>')
    412    set_extmark(ns, 10, 0, 2) -- this shouldn't be found
    413    set_extmark(ns, 11, 2, 1) -- this shouldn't be found
    414    set_extmark(ns, marks[1], 0, 4) -- check col > our upper bound
    415    set_extmark(ns, marks[2], 1, 1) -- check col < lower bound
    416    set_extmark(ns, marks[3], 2, 0) -- check is inclusive
    417    eq(
    418      { { marks[1], 0, 4 }, { marks[2], 1, 1 }, { marks[3], 2, 0 } },
    419      get_extmarks(ns, { 0, 3 }, { 2, 0 })
    420    )
    421  end)
    422 
    423  it('get_marks works in reverse when mark col < lower col', function()
    424    feed('A<cr>12345<esc>')
    425    feed('A<cr>12345<esc>')
    426    set_extmark(ns, 10, 0, 1) -- this shouldn't be found
    427    set_extmark(ns, 11, 2, 4) -- this shouldn't be found
    428    set_extmark(ns, marks[1], 2, 1) -- check col < our lower bound
    429    set_extmark(ns, marks[2], 1, 4) -- check col > upper bound
    430    set_extmark(ns, marks[3], 0, 2) -- check is inclusive
    431    local rv = get_extmarks(ns, { 2, 3 }, { 0, 2 })
    432    eq({ { marks[1], 2, 1 }, { marks[2], 1, 4 }, { marks[3], 0, 2 } }, rv)
    433    -- doesn't include paired marks whose start pos > lower bound twice
    434    -- and returns mark overlapping start pos but not end pos
    435    local m1 = set_extmark(ns, nil, 0, 0, { end_row = 1, end_col = 4 })
    436    local m2 = set_extmark(ns, nil, 0, 0, { end_row = 1, end_col = 2 })
    437    local m3 = set_extmark(ns, nil, 1, 0, { end_row = 1, end_col = 4 })
    438    local m4 = set_extmark(ns, nil, 1, 2, { end_row = 1, end_col = 4 })
    439    rv = get_extmarks(ns, { 1, 3 }, { 1, 2 }, { overlap = true })
    440    eq({ { m4, 1, 2 }, { m3, 1, 0 }, { m2, 0, 0 }, { m1, 0, 0 } }, rv)
    441  end)
    442 
    443  it('get_marks limit=0 returns nothing', function()
    444    set_extmark(ns, marks[1], positions[1][1], positions[1][2])
    445    local rv = get_extmarks(ns, { -1, -1 }, { -1, -1 }, { limit = 0 })
    446    eq({}, rv)
    447  end)
    448 
    449  it('marks move with line insertations', function()
    450    set_extmark(ns, marks[1], 0, 0)
    451    feed('yyP')
    452    check_undo_redo(ns, marks[1], 0, 0, 1, 0)
    453  end)
    454 
    455  it('marks move with multiline insertations', function()
    456    feed('a<cr>22<cr>33<esc>')
    457    set_extmark(ns, marks[1], 1, 1)
    458    feed('ggVGyP')
    459    check_undo_redo(ns, marks[1], 1, 1, 4, 1)
    460  end)
    461 
    462  it('marks move with line join', function()
    463    -- do_join in ops.c
    464    feed('a<cr>222<esc>')
    465    set_extmark(ns, marks[1], 1, 0)
    466    feed('ggJ')
    467    check_undo_redo(ns, marks[1], 1, 0, 0, 6)
    468  end)
    469 
    470  it('join works when no marks are present', function()
    471    screen = Screen.new(15, 10)
    472    feed('a<cr>1<esc>')
    473    feed('kJ')
    474    -- This shouldn't seg fault
    475    screen:expect([[
    476      12345^ 1        |
    477      {1:~              }|*8
    478                     |
    479    ]])
    480  end)
    481 
    482  it('marks move with multiline join', function()
    483    -- do_join in ops.c
    484    feed('a<cr>222<cr>333<cr>444<esc>')
    485    set_extmark(ns, marks[1], 3, 0)
    486    feed('2GVGJ')
    487    check_undo_redo(ns, marks[1], 3, 0, 1, 8)
    488  end)
    489 
    490  it('marks move with line deletes', function()
    491    feed('a<cr>222<cr>333<cr>444<esc>')
    492    set_extmark(ns, marks[1], 2, 1)
    493    feed('ggjdd')
    494    check_undo_redo(ns, marks[1], 2, 1, 1, 1)
    495  end)
    496 
    497  it('marks move with multiline deletes', function()
    498    feed('a<cr>222<cr>333<cr>444<esc>')
    499    set_extmark(ns, marks[1], 3, 0)
    500    feed('gg2dd')
    501    check_undo_redo(ns, marks[1], 3, 0, 1, 0)
    502    -- regression test, undoing multiline delete when mark is on row 1
    503    feed('ugg3dd')
    504    check_undo_redo(ns, marks[1], 3, 0, 0, 0)
    505  end)
    506 
    507  it('marks move with open line', function()
    508    -- open_line in change.c
    509    -- testing marks below are also moved
    510    feed('yyP')
    511    set_extmark(ns, marks[1], 0, 4)
    512    set_extmark(ns, marks[2], 1, 4)
    513    feed('1G<s-o><esc>')
    514    check_undo_redo(ns, marks[1], 0, 4, 1, 4)
    515    check_undo_redo(ns, marks[2], 1, 4, 2, 4)
    516    feed('2Go<esc>')
    517    check_undo_redo(ns, marks[1], 1, 4, 1, 4)
    518    check_undo_redo(ns, marks[2], 2, 4, 3, 4)
    519  end)
    520 
    521  it('marks move with char inserts', function()
    522    -- insertchar in edit.c (the ins_str branch)
    523    screen = Screen.new(15, 10)
    524    set_extmark(ns, marks[1], 0, 3)
    525    feed('0')
    526    insert('abc')
    527    screen:expect([[
    528      ab^c12345       |
    529      {1:~              }|*8
    530                     |
    531    ]])
    532    local rv = get_extmark_by_id(ns, marks[1])
    533    eq({ 0, 6 }, rv)
    534    check_undo_redo(ns, marks[1], 0, 3, 0, 6)
    535  end)
    536 
    537  -- gravity right as definted in tk library
    538  it('marks have gravity right', function()
    539    -- insertchar in edit.c (the ins_str branch)
    540    set_extmark(ns, marks[1], 0, 2)
    541    feed('03l')
    542    insert('X')
    543    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
    544 
    545    -- check multibyte chars
    546    feed('03l<esc>')
    547    insert('~~')
    548    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
    549  end)
    550 
    551  it('we can insert multibyte chars', function()
    552    -- insertchar in edit.c
    553    feed('a<cr>12345<esc>')
    554    set_extmark(ns, marks[1], 1, 2)
    555    -- Insert a fullwidth (two col) tilde, NICE
    556    feed('0i~<esc>')
    557    check_undo_redo(ns, marks[1], 1, 2, 1, 5)
    558  end)
    559 
    560  it('marks move with blockwise inserts', function()
    561    -- op_insert in ops.c
    562    feed('a<cr>12345<esc>')
    563    set_extmark(ns, marks[1], 1, 2)
    564    feed('0<c-v>lkI9<esc>')
    565    check_undo_redo(ns, marks[1], 1, 2, 1, 3)
    566  end)
    567 
    568  it('marks move with line splits (using enter)', function()
    569    -- open_line in change.c
    570    -- testing marks below are also moved
    571    feed('yyP')
    572    set_extmark(ns, marks[1], 0, 4)
    573    set_extmark(ns, marks[2], 1, 4)
    574    feed('1Gla<cr><esc>')
    575    check_undo_redo(ns, marks[1], 0, 4, 1, 2)
    576    check_undo_redo(ns, marks[2], 1, 4, 2, 4)
    577  end)
    578 
    579  it('marks at last line move on insert new line', function()
    580    -- open_line in change.c
    581    set_extmark(ns, marks[1], 0, 4)
    582    feed('0i<cr><esc>')
    583    check_undo_redo(ns, marks[1], 0, 4, 1, 4)
    584  end)
    585 
    586  it('yet again marks move with line splits', function()
    587    -- the first test above wasn't catching all errors..
    588    feed('A67890<esc>')
    589    set_extmark(ns, marks[1], 0, 4)
    590    feed('04li<cr><esc>')
    591    check_undo_redo(ns, marks[1], 0, 4, 1, 0)
    592  end)
    593 
    594  it('and one last time line splits...', function()
    595    set_extmark(ns, marks[1], 0, 1)
    596    set_extmark(ns, marks[2], 0, 2)
    597    feed('02li<cr><esc>')
    598    check_undo_redo(ns, marks[1], 0, 1, 0, 1)
    599    check_undo_redo(ns, marks[2], 0, 2, 1, 0)
    600  end)
    601 
    602  it('multiple marks move with mark splits', function()
    603    set_extmark(ns, marks[1], 0, 1)
    604    set_extmark(ns, marks[2], 0, 3)
    605    feed('0li<cr><esc>')
    606    check_undo_redo(ns, marks[1], 0, 1, 1, 0)
    607    check_undo_redo(ns, marks[2], 0, 3, 1, 2)
    608  end)
    609 
    610  it('deleting right before a mark works', function()
    611    -- op_delete in ops.c
    612    set_extmark(ns, marks[1], 0, 2)
    613    feed('0lx')
    614    check_undo_redo(ns, marks[1], 0, 2, 0, 1)
    615  end)
    616 
    617  it('deleting right after a mark works', function()
    618    -- op_delete in ops.c
    619    set_extmark(ns, marks[1], 0, 2)
    620    feed('02lx')
    621    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
    622  end)
    623 
    624  it('marks move with char deletes', function()
    625    -- op_delete in ops.c
    626    set_extmark(ns, marks[1], 0, 2)
    627    feed('02dl')
    628    check_undo_redo(ns, marks[1], 0, 2, 0, 0)
    629    -- from the other side (nothing should happen)
    630    feed('$x')
    631    check_undo_redo(ns, marks[1], 0, 0, 0, 0)
    632  end)
    633 
    634  it('marks move with char deletes over a range', function()
    635    -- op_delete in ops.c
    636    set_extmark(ns, marks[1], 0, 2)
    637    set_extmark(ns, marks[2], 0, 3)
    638    feed('0l3dl<esc>')
    639    check_undo_redo(ns, marks[1], 0, 2, 0, 1)
    640    check_undo_redo(ns, marks[2], 0, 3, 0, 1)
    641    -- delete 1, nothing should happen to our marks
    642    feed('u')
    643    feed('$x')
    644    check_undo_redo(ns, marks[2], 0, 3, 0, 3)
    645  end)
    646 
    647  it('deleting marks at end of line works', function()
    648    set_extmark(ns, marks[1], 0, 4)
    649    feed('$x')
    650    check_undo_redo(ns, marks[1], 0, 4, 0, 4)
    651    -- check the copy happened correctly on delete at eol
    652    feed('$x')
    653    check_undo_redo(ns, marks[1], 0, 4, 0, 3)
    654    feed('u')
    655    check_undo_redo(ns, marks[1], 0, 4, 0, 4)
    656  end)
    657 
    658  it('marks move with blockwise deletes', function()
    659    -- op_delete in ops.c
    660    feed('a<cr>12345<esc>')
    661    set_extmark(ns, marks[1], 1, 4)
    662    feed('h<c-v>hhkd')
    663    check_undo_redo(ns, marks[1], 1, 4, 1, 1)
    664  end)
    665 
    666  it('marks move with blockwise deletes over a range', function()
    667    -- op_delete in ops.c
    668    feed('a<cr>12345<esc>')
    669    set_extmark(ns, marks[1], 0, 1)
    670    set_extmark(ns, marks[2], 0, 3)
    671    set_extmark(ns, marks[3], 1, 2)
    672    feed('0<c-v>k3lx')
    673    check_undo_redo(ns, marks[1], 0, 1, 0, 0)
    674    check_undo_redo(ns, marks[2], 0, 3, 0, 0)
    675    check_undo_redo(ns, marks[3], 1, 2, 1, 0)
    676    -- delete 1, nothing should happen to our marks
    677    feed('u')
    678    feed('$<c-v>jx')
    679    check_undo_redo(ns, marks[2], 0, 3, 0, 3)
    680    check_undo_redo(ns, marks[3], 1, 2, 1, 2)
    681  end)
    682 
    683  it('works with char deletes over multilines', function()
    684    feed('a<cr>12345<cr>test-me<esc>')
    685    set_extmark(ns, marks[1], 2, 5)
    686    feed('gg')
    687    feed('dv?-m?<cr>')
    688    check_undo_redo(ns, marks[1], 2, 5, 0, 0)
    689  end)
    690 
    691  it('marks outside of deleted range move with visual char deletes', function()
    692    -- op_delete in ops.c
    693    set_extmark(ns, marks[1], 0, 3)
    694    feed('0vx<esc>')
    695    check_undo_redo(ns, marks[1], 0, 3, 0, 2)
    696 
    697    feed('u')
    698    feed('0vlx<esc>')
    699    check_undo_redo(ns, marks[1], 0, 3, 0, 1)
    700 
    701    feed('u')
    702    feed('0v2lx<esc>')
    703    check_undo_redo(ns, marks[1], 0, 3, 0, 0)
    704 
    705    -- from the other side (nothing should happen)
    706    feed('$vx')
    707    check_undo_redo(ns, marks[1], 0, 0, 0, 0)
    708  end)
    709 
    710  it('marks outside of deleted range move with char deletes', function()
    711    -- op_delete in ops.c
    712    set_extmark(ns, marks[1], 0, 3)
    713    feed('0x<esc>')
    714    check_undo_redo(ns, marks[1], 0, 3, 0, 2)
    715 
    716    feed('u')
    717    feed('02x<esc>')
    718    check_undo_redo(ns, marks[1], 0, 3, 0, 1)
    719 
    720    feed('u')
    721    feed('0v3lx<esc>')
    722    check_undo_redo(ns, marks[1], 0, 3, 0, 0)
    723 
    724    -- from the other side (nothing should happen)
    725    feed('u')
    726    feed('$vx')
    727    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
    728  end)
    729 
    730  it('marks move with P(backward) paste', function()
    731    -- do_put in ops.c
    732    feed('0iabc<esc>')
    733    set_extmark(ns, marks[1], 0, 7)
    734    feed('0veyP')
    735    check_undo_redo(ns, marks[1], 0, 7, 0, 15)
    736  end)
    737 
    738  it('marks move with p(forward) paste', function()
    739    -- do_put in ops.c
    740    feed('0iabc<esc>')
    741    set_extmark(ns, marks[1], 0, 7)
    742    feed('0veyp')
    743    check_undo_redo(ns, marks[1], 0, 7, 0, 15)
    744  end)
    745 
    746  it('marks move with blockwise P(backward) paste', function()
    747    -- do_put in ops.c
    748    feed('a<cr>12345<esc>')
    749    set_extmark(ns, marks[1], 1, 4)
    750    feed('<c-v>hhkyP<esc>')
    751    check_undo_redo(ns, marks[1], 1, 4, 1, 7)
    752  end)
    753 
    754  it('marks move with blockwise p(forward) paste', function()
    755    -- do_put in ops.c
    756    feed('a<cr>12345<esc>')
    757    set_extmark(ns, marks[1], 1, 4)
    758    feed('<c-v>hhkyp<esc>')
    759    check_undo_redo(ns, marks[1], 1, 4, 1, 7)
    760  end)
    761 
    762  describe('multiline regions', function()
    763    before_each(function()
    764      feed('dd')
    765      -- Achtung: code has been spiced with some unicode,
    766      -- to make life more interesting.
    767      -- luacheck whines about TABs inside strings for whatever reason.
    768      -- luacheck: push ignore 621
    769      insert([[
    770        static int nlua_rpcrequest(lua_State *lstate)
    771        {
    772          Ïf (!nlua_is_deferred_safe(lstate)) {
    773        	// strictly not allowed
    774            Яetörn luaL_error(lstate, e_fast_api_disabled, "rpcrequest");
    775          }
    776          return nlua_rpc(lstate, true);
    777        }]])
    778      -- luacheck: pop
    779    end)
    780 
    781    it('delete', function()
    782      local pos1 = {
    783        { 2, 4 },
    784        { 2, 12 },
    785        { 2, 13 },
    786        { 2, 14 },
    787        { 2, 25 },
    788        { 4, 8 },
    789        { 4, 10 },
    790        { 4, 20 },
    791        { 5, 3 },
    792        { 6, 10 },
    793      }
    794      local ids = batch_set(ns, pos1)
    795      batch_check(ns, ids, pos1)
    796      feed('3Gfiv2+ftd')
    797      batch_check_undo_redo(ns, ids, pos1, {
    798        { 2, 4 },
    799        { 2, 12 },
    800        { 2, 13 },
    801        { 2, 13 },
    802        { 2, 13 },
    803        { 2, 13 },
    804        { 2, 15 },
    805        { 2, 25 },
    806        { 3, 3 },
    807        { 4, 10 },
    808      })
    809    end)
    810 
    811    it('can get overlapping extmarks', function()
    812      set_extmark(ns, 1, 0, 0, { end_row = 5, end_col = 0 })
    813      set_extmark(ns, 2, 2, 5, { end_row = 2, end_col = 30 })
    814      set_extmark(ns, 3, 0, 5, { end_row = 2, end_col = 10 })
    815      set_extmark(ns, 4, 0, 0, { end_row = 1, end_col = 0 })
    816      eq({ { 2, 2, 5 } }, get_extmarks(ns, { 2, 0 }, { 2, -1 }, { overlap = false }))
    817      eq(
    818        { { 1, 0, 0 }, { 3, 0, 5 }, { 2, 2, 5 } },
    819        get_extmarks(ns, { 2, 0 }, { 2, -1 }, { overlap = true })
    820      )
    821    end)
    822 
    823    it('limits overlap results', function()
    824      set_extmark(ns, 1, 0, 0, { end_row = 5, end_col = 0 })
    825      set_extmark(ns, 2, 2, 5, { end_row = 2, end_col = 30 })
    826      set_extmark(ns, 3, 0, 5, { end_row = 2, end_col = 10 })
    827      set_extmark(ns, 4, 0, 0, { end_row = 1, end_col = 0 })
    828      local rv = get_extmarks(ns, { 2, 0 }, { 2, -1 }, { overlap = true, limit = 1 })
    829      eq(1, #rv)
    830    end)
    831  end)
    832 
    833  it('replace works', function()
    834    set_extmark(ns, marks[1], 0, 2)
    835    feed('0r2')
    836    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
    837  end)
    838 
    839  it('blockwise replace works', function()
    840    feed('a<cr>12345<esc>')
    841    set_extmark(ns, marks[1], 0, 2)
    842    feed('0<c-v>llkr1<esc>')
    843    check_undo_redo(ns, marks[1], 0, 2, 0, 3)
    844  end)
    845 
    846  it('shift line', function()
    847    -- shift_line in ops.c
    848    feed(':set shiftwidth=4<cr><esc>')
    849    set_extmark(ns, marks[1], 0, 2)
    850    feed('0>>')
    851    check_undo_redo(ns, marks[1], 0, 2, 0, 6)
    852    expect('    12345')
    853 
    854    feed('>>')
    855    -- this is counter-intuitive. But what happens
    856    -- is that 4 spaces gets extended to one tab (== 8 spaces)
    857    check_undo_redo(ns, marks[1], 0, 6, 0, 3)
    858    expect('\t12345')
    859 
    860    feed('<LT><LT>') -- have to escape, same as <<
    861    check_undo_redo(ns, marks[1], 0, 3, 0, 6)
    862  end)
    863 
    864  it('blockwise shift', function()
    865    -- shift_block in ops.c
    866    feed(':set shiftwidth=4<cr><esc>')
    867    feed('a<cr>12345<esc>')
    868    set_extmark(ns, marks[1], 1, 2)
    869    feed('0<c-v>k>')
    870    check_undo_redo(ns, marks[1], 1, 2, 1, 6)
    871    feed('<c-v>j>')
    872    expect('\t12345\n\t12345')
    873    check_undo_redo(ns, marks[1], 1, 6, 1, 3)
    874 
    875    feed('<c-v>j<LT>')
    876    check_undo_redo(ns, marks[1], 1, 3, 1, 6)
    877  end)
    878 
    879  it('tab works with expandtab', function()
    880    -- ins_tab in edit.c
    881    feed(':set expandtab<cr><esc>')
    882    feed(':set shiftwidth=2<cr><esc>')
    883    set_extmark(ns, marks[1], 0, 2)
    884    feed('0i<tab><tab><esc>')
    885    check_undo_redo(ns, marks[1], 0, 2, 0, 6)
    886  end)
    887 
    888  it('tabs work', function()
    889    -- ins_tab in edit.c
    890    feed(':set noexpandtab<cr><esc>')
    891    feed(':set shiftwidth=2<cr><esc>')
    892    feed(':set softtabstop=2<cr><esc>')
    893    feed(':set tabstop=8<cr><esc>')
    894    set_extmark(ns, marks[1], 0, 2)
    895    feed('0i<tab><esc>')
    896    check_undo_redo(ns, marks[1], 0, 2, 0, 4)
    897    feed('0iX<tab><esc>')
    898    check_undo_redo(ns, marks[1], 0, 4, 0, 6)
    899  end)
    900 
    901  it('marks move when using :move', function()
    902    set_extmark(ns, marks[1], 0, 0)
    903    feed('A<cr>2<esc>:1move 2<cr><esc>')
    904    check_undo_redo(ns, marks[1], 0, 0, 1, 0)
    905    -- test codepath when moving lines up
    906    feed(':2move 0<cr><esc>')
    907    check_undo_redo(ns, marks[1], 1, 0, 0, 0)
    908  end)
    909 
    910  it('marks move when using :move part 2', function()
    911    -- make sure we didn't get lucky with the math...
    912    feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>')
    913    set_extmark(ns, marks[1], 1, 0)
    914    feed(':2,3move 5<cr><esc>')
    915    check_undo_redo(ns, marks[1], 1, 0, 3, 0)
    916    -- test codepath when moving lines up
    917    feed(':4,5move 1<cr><esc>')
    918    check_undo_redo(ns, marks[1], 3, 0, 1, 0)
    919  end)
    920 
    921  it('undo and redo of set and unset marks', function()
    922    -- Force a new undo head
    923    feed('o<esc>')
    924    set_extmark(ns, marks[1], 0, 1)
    925    feed('o<esc>')
    926    set_extmark(ns, marks[2], 0, -1)
    927    set_extmark(ns, marks[3], 0, -1)
    928 
    929    feed('u')
    930    local rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    931    eq(3, #rv)
    932 
    933    feed('<c-r>')
    934    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    935    eq(3, #rv)
    936 
    937    -- Test updates
    938    feed('o<esc>')
    939    set_extmark(ns, marks[1], positions[1][1], positions[1][2])
    940    rv = get_extmarks(ns, marks[1], marks[1], { limit = 1 })
    941    eq(1, #rv)
    942    feed('u')
    943    feed('<c-r>')
    944    -- old value is NOT kept in history
    945    check_undo_redo(
    946      ns,
    947      marks[1],
    948      positions[1][1],
    949      positions[1][2],
    950      positions[1][1],
    951      positions[1][2]
    952    )
    953 
    954    -- Test unset
    955    feed('o<esc>')
    956    api.nvim_buf_del_extmark(0, ns, marks[3])
    957    feed('u')
    958    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    959    -- undo does NOT restore deleted marks
    960    eq(2, #rv)
    961    feed('<c-r>')
    962    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    963    eq(2, #rv)
    964  end)
    965 
    966  it('undo and redo of marks deleted during edits', function()
    967    -- test extmark_adjust
    968    feed('A<cr>12345<esc>')
    969    set_extmark(ns, marks[1], 1, 2)
    970    feed('dd')
    971    check_undo_redo(ns, marks[1], 1, 2, 1, 0)
    972  end)
    973 
    974  it('namespaces work properly', function()
    975    local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
    976    eq(1, rv)
    977    rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2])
    978    eq(1, rv)
    979    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
    980    eq(1, #rv)
    981    rv = get_extmarks(ns2, { 0, 0 }, { -1, -1 })
    982    eq(1, #rv)
    983 
    984    -- Set more marks for testing the ranges
    985    set_extmark(ns, marks[2], positions[2][1], positions[2][2])
    986    set_extmark(ns, marks[3], positions[3][1], positions[3][2])
    987    set_extmark(ns2, marks[2], positions[2][1], positions[2][2])
    988    set_extmark(ns2, marks[3], positions[3][1], positions[3][2])
    989 
    990    -- get_next (limit set)
    991    rv = get_extmarks(ns, { 0, 0 }, positions[2], { limit = 1 })
    992    eq(1, #rv)
    993    rv = get_extmarks(ns2, { 0, 0 }, positions[2], { limit = 1 })
    994    eq(1, #rv)
    995    -- reverse (limit set)
    996    rv = get_extmarks(ns, positions[1], { 0, 0 }, { limit = 1 })
    997    eq(1, #rv)
    998    rv = get_extmarks(ns2, positions[1], { 0, 0 }, { limit = 1 })
    999    eq(1, #rv)
   1000 
   1001    -- get_next (no limit)
   1002    rv = get_extmarks(ns, positions[1], positions[2])
   1003    eq(2, #rv)
   1004    rv = get_extmarks(ns2, positions[1], positions[2])
   1005    eq(2, #rv)
   1006    -- reverse (no limit)
   1007    rv = get_extmarks(ns, positions[2], positions[1])
   1008    eq(2, #rv)
   1009    rv = get_extmarks(ns2, positions[2], positions[1])
   1010    eq(2, #rv)
   1011 
   1012    api.nvim_buf_del_extmark(0, ns, marks[1])
   1013    rv = get_extmarks(ns, { 0, 0 }, { -1, -1 })
   1014    eq(2, #rv)
   1015    api.nvim_buf_del_extmark(0, ns2, marks[1])
   1016    rv = get_extmarks(ns2, { 0, 0 }, { -1, -1 })
   1017    eq(2, #rv)
   1018  end)
   1019 
   1020  it('mark set can create unique identifiers', function()
   1021    -- create mark with id 1
   1022    eq(1, set_extmark(ns, 1, positions[1][1], positions[1][2]))
   1023    -- ask for unique id, it should be the next one, i e 2
   1024    eq(2, set_extmark(ns, 0, positions[1][1], positions[1][2]))
   1025    eq(3, set_extmark(ns, 3, positions[2][1], positions[2][2]))
   1026    eq(4, set_extmark(ns, 0, positions[1][1], positions[1][2]))
   1027 
   1028    -- mixing manual and allocated id:s are not recommended, but it should
   1029    -- do something reasonable
   1030    eq(6, set_extmark(ns, 6, positions[2][1], positions[2][2]))
   1031    eq(7, set_extmark(ns, 0, positions[1][1], positions[1][2]))
   1032    eq(8, set_extmark(ns, 0, positions[1][1], positions[1][2]))
   1033  end)
   1034 
   1035  it('auto indenting with enter works', function()
   1036    -- op_reindent in ops.c
   1037    feed(':set cindent<cr><esc>')
   1038    feed(':set autoindent<cr><esc>')
   1039    feed(':set shiftwidth=2<cr><esc>')
   1040    feed('0iint <esc>A {1M1<esc>b<esc>')
   1041    -- Set the mark on the M, should move..
   1042    set_extmark(ns, marks[1], 0, 12)
   1043    -- Set the mark before the cursor, should stay there
   1044    set_extmark(ns, marks[2], 0, 10)
   1045    feed('i<cr><esc>')
   1046    local rv = get_extmark_by_id(ns, marks[1])
   1047    eq({ 1, 3 }, rv)
   1048    rv = get_extmark_by_id(ns, marks[2])
   1049    eq({ 0, 10 }, rv)
   1050    check_undo_redo(ns, marks[1], 0, 12, 1, 3)
   1051  end)
   1052 
   1053  it('auto indenting entire line works', function()
   1054    feed(':set cindent<cr><esc>')
   1055    feed(':set autoindent<cr><esc>')
   1056    feed(':set shiftwidth=2<cr><esc>')
   1057    -- <c-f> will force an indent of 2
   1058    feed('0iint <esc>A {<cr><esc>0i1M1<esc>')
   1059    set_extmark(ns, marks[1], 1, 1)
   1060    feed('0i<c-f><esc>')
   1061    local rv = get_extmark_by_id(ns, marks[1])
   1062    eq({ 1, 3 }, rv)
   1063    check_undo_redo(ns, marks[1], 1, 1, 1, 3)
   1064    -- now check when cursor at eol
   1065    feed('uA<c-f><esc>')
   1066    rv = get_extmark_by_id(ns, marks[1])
   1067    eq({ 1, 3 }, rv)
   1068  end)
   1069 
   1070  it('removing auto indenting with <C-D> works', function()
   1071    feed(':set cindent<cr><esc>')
   1072    feed(':set autoindent<cr><esc>')
   1073    feed(':set shiftwidth=2<cr><esc>')
   1074    feed('0i<tab><esc>')
   1075    set_extmark(ns, marks[1], 0, 3)
   1076    feed('bi<c-d><esc>')
   1077    local rv = get_extmark_by_id(ns, marks[1])
   1078    eq({ 0, 1 }, rv)
   1079    check_undo_redo(ns, marks[1], 0, 3, 0, 1)
   1080    -- check when cursor at eol
   1081    feed('uA<c-d><esc>')
   1082    rv = get_extmark_by_id(ns, marks[1])
   1083    eq({ 0, 1 }, rv)
   1084  end)
   1085 
   1086  it('indenting multiple lines with = works', function()
   1087    feed(':set cindent<cr><esc>')
   1088    feed(':set autoindent<cr><esc>')
   1089    feed(':set shiftwidth=2<cr><esc>')
   1090    feed('0iint <esc>A {<cr><bs>1M1<cr><bs>2M2<esc>')
   1091    set_extmark(ns, marks[1], 1, 1)
   1092    set_extmark(ns, marks[2], 2, 1)
   1093    feed('=gg')
   1094    check_undo_redo(ns, marks[1], 1, 1, 1, 3)
   1095    check_undo_redo(ns, marks[2], 2, 1, 2, 5)
   1096  end)
   1097 
   1098  it('substitutes by deleting inside the replace matches', function()
   1099    -- do_sub in ex_cmds.c
   1100    set_extmark(ns, marks[1], 0, 2)
   1101    set_extmark(ns, marks[2], 0, 3)
   1102    feed(':s/34/xx<cr>')
   1103    check_undo_redo(ns, marks[1], 0, 2, 0, 4)
   1104    check_undo_redo(ns, marks[2], 0, 3, 0, 4)
   1105  end)
   1106 
   1107  it('substitutes when insert text > deleted', function()
   1108    -- do_sub in ex_cmds.c
   1109    set_extmark(ns, marks[1], 0, 2)
   1110    set_extmark(ns, marks[2], 0, 3)
   1111    feed(':s/34/xxx<cr>')
   1112    check_undo_redo(ns, marks[1], 0, 2, 0, 5)
   1113    check_undo_redo(ns, marks[2], 0, 3, 0, 5)
   1114  end)
   1115 
   1116  it('substitutes when marks around eol', function()
   1117    -- do_sub in ex_cmds.c
   1118    set_extmark(ns, marks[1], 0, 4)
   1119    set_extmark(ns, marks[2], 0, 5)
   1120    feed(':s/5/xxx<cr>')
   1121    check_undo_redo(ns, marks[1], 0, 4, 0, 7)
   1122    check_undo_redo(ns, marks[2], 0, 5, 0, 7)
   1123  end)
   1124 
   1125  it('substitutes over range insert text > deleted', function()
   1126    -- do_sub in ex_cmds.c
   1127    feed('A<cr>x34xx<esc>')
   1128    feed('A<cr>xxx34<esc>')
   1129    set_extmark(ns, marks[1], 0, 2)
   1130    set_extmark(ns, marks[2], 1, 1)
   1131    set_extmark(ns, marks[3], 2, 4)
   1132    feed(':1,3s/34/xxx<cr><esc>')
   1133    check_undo_redo(ns, marks[1], 0, 2, 0, 5)
   1134    check_undo_redo(ns, marks[2], 1, 1, 1, 4)
   1135    check_undo_redo(ns, marks[3], 2, 4, 2, 6)
   1136  end)
   1137 
   1138  it('substitutes multiple matches in a line', function()
   1139    -- do_sub in ex_cmds.c
   1140    feed('ddi3x3x3<esc>')
   1141    set_extmark(ns, marks[1], 0, 0)
   1142    set_extmark(ns, marks[2], 0, 2)
   1143    set_extmark(ns, marks[3], 0, 4)
   1144    feed(':s/3/yy/g<cr><esc>')
   1145    check_undo_redo(ns, marks[1], 0, 0, 0, 2)
   1146    check_undo_redo(ns, marks[2], 0, 2, 0, 5)
   1147    check_undo_redo(ns, marks[3], 0, 4, 0, 8)
   1148  end)
   1149 
   1150  it('substitutes over multiple lines with newline in pattern', function()
   1151    feed('A<cr>67890<cr>xx<esc>')
   1152    set_extmark(ns, marks[1], 0, 3)
   1153    set_extmark(ns, marks[2], 0, 4)
   1154    set_extmark(ns, marks[3], 1, 0)
   1155    set_extmark(ns, marks[4], 1, 5)
   1156    set_extmark(ns, marks[5], 2, 0)
   1157    feed([[:1,2s:5\n:5 <cr>]])
   1158    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1159    check_undo_redo(ns, marks[2], 0, 4, 0, 6)
   1160    check_undo_redo(ns, marks[3], 1, 0, 0, 6)
   1161    check_undo_redo(ns, marks[4], 1, 5, 0, 11)
   1162    check_undo_redo(ns, marks[5], 2, 0, 1, 0)
   1163  end)
   1164 
   1165  it('inserting', function()
   1166    feed('A<cr>67890<cr>xx<esc>')
   1167    set_extmark(ns, marks[1], 0, 3)
   1168    set_extmark(ns, marks[2], 0, 4)
   1169    set_extmark(ns, marks[3], 1, 0)
   1170    set_extmark(ns, marks[4], 1, 5)
   1171    set_extmark(ns, marks[5], 2, 0)
   1172    set_extmark(ns, marks[6], 1, 2)
   1173    feed([[:1,2s:5\n67:X<cr>]])
   1174    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1175    check_undo_redo(ns, marks[2], 0, 4, 0, 5)
   1176    check_undo_redo(ns, marks[3], 1, 0, 0, 5)
   1177    check_undo_redo(ns, marks[4], 1, 5, 0, 8)
   1178    check_undo_redo(ns, marks[5], 2, 0, 1, 0)
   1179    check_undo_redo(ns, marks[6], 1, 2, 0, 5)
   1180  end)
   1181 
   1182  it('substitutes with multiple newlines in pattern', function()
   1183    feed('A<cr>67890<cr>xx<esc>')
   1184    set_extmark(ns, marks[1], 0, 4)
   1185    set_extmark(ns, marks[2], 0, 5)
   1186    set_extmark(ns, marks[3], 1, 0)
   1187    set_extmark(ns, marks[4], 1, 5)
   1188    set_extmark(ns, marks[5], 2, 0)
   1189    feed([[:1,2s:\n.*\n:X<cr>]])
   1190    check_undo_redo(ns, marks[1], 0, 4, 0, 4)
   1191    check_undo_redo(ns, marks[2], 0, 5, 0, 6)
   1192    check_undo_redo(ns, marks[3], 1, 0, 0, 6)
   1193    check_undo_redo(ns, marks[4], 1, 5, 0, 6)
   1194    check_undo_redo(ns, marks[5], 2, 0, 0, 6)
   1195  end)
   1196 
   1197  it('substitutes over multiple lines with replace in substitution', function()
   1198    feed('A<cr>67890<cr>xx<esc>')
   1199    set_extmark(ns, marks[1], 0, 1)
   1200    set_extmark(ns, marks[2], 0, 2)
   1201    set_extmark(ns, marks[3], 0, 4)
   1202    set_extmark(ns, marks[4], 1, 0)
   1203    set_extmark(ns, marks[5], 2, 0)
   1204    feed([[:1,2s:3:\r<cr>]])
   1205    check_undo_redo(ns, marks[1], 0, 1, 0, 1)
   1206    check_undo_redo(ns, marks[2], 0, 2, 1, 0)
   1207    check_undo_redo(ns, marks[3], 0, 4, 1, 1)
   1208    check_undo_redo(ns, marks[4], 1, 0, 2, 0)
   1209    check_undo_redo(ns, marks[5], 2, 0, 3, 0)
   1210    feed('u')
   1211    feed([[:1,2s:3:\rxx<cr>]])
   1212    eq({ 1, 3 }, get_extmark_by_id(ns, marks[3]))
   1213  end)
   1214 
   1215  it('substitutes over multiple lines with replace in substitution', function()
   1216    feed('A<cr>x3<cr>xx<esc>')
   1217    set_extmark(ns, marks[1], 1, 0)
   1218    set_extmark(ns, marks[2], 1, 1)
   1219    set_extmark(ns, marks[3], 1, 2)
   1220    feed([[:2,2s:3:\r<cr>]])
   1221    check_undo_redo(ns, marks[1], 1, 0, 1, 0)
   1222    check_undo_redo(ns, marks[2], 1, 1, 2, 0)
   1223    check_undo_redo(ns, marks[3], 1, 2, 2, 0)
   1224  end)
   1225 
   1226  it('substitutes over multiple lines with replace in substitution', function()
   1227    feed('A<cr>x3<cr>xx<esc>')
   1228    set_extmark(ns, marks[1], 0, 1)
   1229    set_extmark(ns, marks[2], 0, 2)
   1230    set_extmark(ns, marks[3], 0, 4)
   1231    set_extmark(ns, marks[4], 1, 1)
   1232    set_extmark(ns, marks[5], 2, 0)
   1233    feed([[:1,2s:3:\r<cr>]])
   1234    check_undo_redo(ns, marks[1], 0, 1, 0, 1)
   1235    check_undo_redo(ns, marks[2], 0, 2, 1, 0)
   1236    check_undo_redo(ns, marks[3], 0, 4, 1, 1)
   1237    check_undo_redo(ns, marks[4], 1, 1, 3, 0)
   1238    check_undo_redo(ns, marks[5], 2, 0, 4, 0)
   1239    feed('u')
   1240    feed([[:1,2s:3:\rxx<cr>]])
   1241    check_undo_redo(ns, marks[3], 0, 4, 1, 3)
   1242  end)
   1243 
   1244  it('substitutes with newline in match and sub, delta is 0', function()
   1245    feed('A<cr>67890<cr>xx<esc>')
   1246    set_extmark(ns, marks[1], 0, 3)
   1247    set_extmark(ns, marks[2], 0, 4)
   1248    set_extmark(ns, marks[3], 0, 5)
   1249    set_extmark(ns, marks[4], 1, 0)
   1250    set_extmark(ns, marks[5], 1, 5)
   1251    set_extmark(ns, marks[6], 2, 0)
   1252    feed([[:1,1s:5\n:\r<cr>]])
   1253    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1254    check_undo_redo(ns, marks[2], 0, 4, 1, 0)
   1255    check_undo_redo(ns, marks[3], 0, 5, 1, 0)
   1256    check_undo_redo(ns, marks[4], 1, 0, 1, 0)
   1257    check_undo_redo(ns, marks[5], 1, 5, 1, 5)
   1258    check_undo_redo(ns, marks[6], 2, 0, 2, 0)
   1259  end)
   1260 
   1261  it('substitutes with newline in match and sub, delta > 0', function()
   1262    feed('A<cr>67890<cr>xx<esc>')
   1263    set_extmark(ns, marks[1], 0, 3)
   1264    set_extmark(ns, marks[2], 0, 4)
   1265    set_extmark(ns, marks[3], 0, 5)
   1266    set_extmark(ns, marks[4], 1, 0)
   1267    set_extmark(ns, marks[5], 1, 5)
   1268    set_extmark(ns, marks[6], 2, 0)
   1269    feed([[:1,1s:5\n:\r\r<cr>]])
   1270    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1271    check_undo_redo(ns, marks[2], 0, 4, 2, 0)
   1272    check_undo_redo(ns, marks[3], 0, 5, 2, 0)
   1273    check_undo_redo(ns, marks[4], 1, 0, 2, 0)
   1274    check_undo_redo(ns, marks[5], 1, 5, 2, 5)
   1275    check_undo_redo(ns, marks[6], 2, 0, 3, 0)
   1276  end)
   1277 
   1278  it('substitutes with newline in match and sub, delta < 0', function()
   1279    feed('A<cr>67890<cr>xx<cr>xx<esc>')
   1280    set_extmark(ns, marks[1], 0, 3)
   1281    set_extmark(ns, marks[2], 0, 4)
   1282    set_extmark(ns, marks[3], 0, 5)
   1283    set_extmark(ns, marks[4], 1, 0)
   1284    set_extmark(ns, marks[5], 1, 5)
   1285    set_extmark(ns, marks[6], 2, 1)
   1286    set_extmark(ns, marks[7], 3, 0)
   1287    feed([[:1,2s:5\n.*\n:\r<cr>]])
   1288    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1289    check_undo_redo(ns, marks[2], 0, 4, 1, 0)
   1290    check_undo_redo(ns, marks[3], 0, 5, 1, 0)
   1291    check_undo_redo(ns, marks[4], 1, 0, 1, 0)
   1292    check_undo_redo(ns, marks[5], 1, 5, 1, 0)
   1293    check_undo_redo(ns, marks[6], 2, 1, 1, 1)
   1294    check_undo_redo(ns, marks[7], 3, 0, 2, 0)
   1295  end)
   1296 
   1297  it('substitutes with backrefs, newline inserted into sub', function()
   1298    feed('A<cr>67890<cr>xx<cr>xx<esc>')
   1299    set_extmark(ns, marks[1], 0, 3)
   1300    set_extmark(ns, marks[2], 0, 4)
   1301    set_extmark(ns, marks[3], 0, 5)
   1302    set_extmark(ns, marks[4], 1, 0)
   1303    set_extmark(ns, marks[5], 1, 5)
   1304    set_extmark(ns, marks[6], 2, 0)
   1305    feed([[:1,1s:5\(\n\):\0\1<cr>]])
   1306    check_undo_redo(ns, marks[1], 0, 3, 0, 3)
   1307    check_undo_redo(ns, marks[2], 0, 4, 2, 0)
   1308    check_undo_redo(ns, marks[3], 0, 5, 2, 0)
   1309    check_undo_redo(ns, marks[4], 1, 0, 2, 0)
   1310    check_undo_redo(ns, marks[5], 1, 5, 2, 5)
   1311    check_undo_redo(ns, marks[6], 2, 0, 3, 0)
   1312  end)
   1313 
   1314  it('substitutes a ^', function()
   1315    set_extmark(ns, marks[1], 0, 0)
   1316    set_extmark(ns, marks[2], 0, 1)
   1317    feed([[:s:^:x<cr>]])
   1318    check_undo_redo(ns, marks[1], 0, 0, 0, 1)
   1319    check_undo_redo(ns, marks[2], 0, 1, 0, 2)
   1320  end)
   1321 
   1322  it('using <c-a> without increase in order of magnitude', function()
   1323    -- do_addsub in ops.c
   1324    feed('ddiabc998xxx<esc>Tc')
   1325    set_extmark(ns, marks[1], 0, 2)
   1326    set_extmark(ns, marks[2], 0, 3)
   1327    set_extmark(ns, marks[3], 0, 5)
   1328    set_extmark(ns, marks[4], 0, 6)
   1329    set_extmark(ns, marks[5], 0, 7)
   1330    feed('<c-a>')
   1331    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1332    check_undo_redo(ns, marks[2], 0, 3, 0, 6)
   1333    check_undo_redo(ns, marks[3], 0, 5, 0, 6)
   1334    check_undo_redo(ns, marks[4], 0, 6, 0, 6)
   1335    check_undo_redo(ns, marks[5], 0, 7, 0, 7)
   1336  end)
   1337 
   1338  it('using <c-a> when increase in order of magnitude', function()
   1339    -- do_addsub in ops.c
   1340    feed('ddiabc999xxx<esc>Tc')
   1341    set_extmark(ns, marks[1], 0, 2)
   1342    set_extmark(ns, marks[2], 0, 3)
   1343    set_extmark(ns, marks[3], 0, 5)
   1344    set_extmark(ns, marks[4], 0, 6)
   1345    set_extmark(ns, marks[5], 0, 7)
   1346    feed('<c-a>')
   1347    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1348    check_undo_redo(ns, marks[2], 0, 3, 0, 7)
   1349    check_undo_redo(ns, marks[3], 0, 5, 0, 7)
   1350    check_undo_redo(ns, marks[4], 0, 6, 0, 7)
   1351    check_undo_redo(ns, marks[5], 0, 7, 0, 8)
   1352  end)
   1353 
   1354  it('using <c-a> when negative and without decrease in order of magnitude', function()
   1355    feed('ddiabc-999xxx<esc>T-')
   1356    set_extmark(ns, marks[1], 0, 2)
   1357    set_extmark(ns, marks[2], 0, 3)
   1358    set_extmark(ns, marks[3], 0, 6)
   1359    set_extmark(ns, marks[4], 0, 7)
   1360    set_extmark(ns, marks[5], 0, 8)
   1361    feed('<c-a>')
   1362    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1363    check_undo_redo(ns, marks[2], 0, 3, 0, 7)
   1364    check_undo_redo(ns, marks[3], 0, 6, 0, 7)
   1365    check_undo_redo(ns, marks[4], 0, 7, 0, 7)
   1366    check_undo_redo(ns, marks[5], 0, 8, 0, 8)
   1367  end)
   1368 
   1369  it('using <c-a> when negative and decrease in order of magnitude', function()
   1370    feed('ddiabc-1000xxx<esc>T-')
   1371    set_extmark(ns, marks[1], 0, 2)
   1372    set_extmark(ns, marks[2], 0, 3)
   1373    set_extmark(ns, marks[3], 0, 7)
   1374    set_extmark(ns, marks[4], 0, 8)
   1375    set_extmark(ns, marks[5], 0, 9)
   1376    feed('<c-a>')
   1377    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1378    check_undo_redo(ns, marks[2], 0, 3, 0, 7)
   1379    check_undo_redo(ns, marks[3], 0, 7, 0, 7)
   1380    check_undo_redo(ns, marks[4], 0, 8, 0, 7)
   1381    check_undo_redo(ns, marks[5], 0, 9, 0, 8)
   1382  end)
   1383 
   1384  it('using <c-x> without decrease in order of magnitude', function()
   1385    -- do_addsub in ops.c
   1386    feed('ddiabc999xxx<esc>Tc')
   1387    set_extmark(ns, marks[1], 0, 2)
   1388    set_extmark(ns, marks[2], 0, 3)
   1389    set_extmark(ns, marks[3], 0, 5)
   1390    set_extmark(ns, marks[4], 0, 6)
   1391    set_extmark(ns, marks[5], 0, 7)
   1392    feed('<c-x>')
   1393    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1394    check_undo_redo(ns, marks[2], 0, 3, 0, 6)
   1395    check_undo_redo(ns, marks[3], 0, 5, 0, 6)
   1396    check_undo_redo(ns, marks[4], 0, 6, 0, 6)
   1397    check_undo_redo(ns, marks[5], 0, 7, 0, 7)
   1398  end)
   1399 
   1400  it('using <c-x> when decrease in order of magnitude', function()
   1401    -- do_addsub in ops.c
   1402    feed('ddiabc1000xxx<esc>Tc')
   1403    set_extmark(ns, marks[1], 0, 2)
   1404    set_extmark(ns, marks[2], 0, 3)
   1405    set_extmark(ns, marks[3], 0, 6)
   1406    set_extmark(ns, marks[4], 0, 7)
   1407    set_extmark(ns, marks[5], 0, 8)
   1408    feed('<c-x>')
   1409    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1410    check_undo_redo(ns, marks[2], 0, 3, 0, 6)
   1411    check_undo_redo(ns, marks[3], 0, 6, 0, 6)
   1412    check_undo_redo(ns, marks[4], 0, 7, 0, 6)
   1413    check_undo_redo(ns, marks[5], 0, 8, 0, 7)
   1414  end)
   1415 
   1416  it('using <c-x> when negative and without increase in order of magnitude', function()
   1417    feed('ddiabc-998xxx<esc>T-')
   1418    set_extmark(ns, marks[1], 0, 2)
   1419    set_extmark(ns, marks[2], 0, 3)
   1420    set_extmark(ns, marks[3], 0, 6)
   1421    set_extmark(ns, marks[4], 0, 7)
   1422    set_extmark(ns, marks[5], 0, 8)
   1423    feed('<c-x>')
   1424    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1425    check_undo_redo(ns, marks[2], 0, 3, 0, 7)
   1426    check_undo_redo(ns, marks[3], 0, 6, 0, 7)
   1427    check_undo_redo(ns, marks[4], 0, 7, 0, 7)
   1428    check_undo_redo(ns, marks[5], 0, 8, 0, 8)
   1429  end)
   1430 
   1431  it('using <c-x> when negative and increase in order of magnitude', function()
   1432    feed('ddiabc-999xxx<esc>T-')
   1433    set_extmark(ns, marks[1], 0, 2)
   1434    set_extmark(ns, marks[2], 0, 3)
   1435    set_extmark(ns, marks[3], 0, 6)
   1436    set_extmark(ns, marks[4], 0, 7)
   1437    set_extmark(ns, marks[5], 0, 8)
   1438    feed('<c-x>')
   1439    check_undo_redo(ns, marks[1], 0, 2, 0, 2)
   1440    check_undo_redo(ns, marks[2], 0, 3, 0, 8)
   1441    check_undo_redo(ns, marks[3], 0, 6, 0, 8)
   1442    check_undo_redo(ns, marks[4], 0, 7, 0, 8)
   1443    check_undo_redo(ns, marks[5], 0, 8, 0, 9)
   1444  end)
   1445 
   1446  it('throws consistent error codes', function()
   1447    local ns_invalid = ns2 + 1 ---@type integer
   1448    local err = string.format("Invalid 'ns_id': %d", ns_invalid)
   1449    eq(err, pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
   1450    eq(err, pcall_err(api.nvim_buf_del_extmark, 0, ns_invalid, marks[1]))
   1451    eq(err, pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
   1452    eq(err, pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
   1453  end)
   1454 
   1455  it('when col = line-length, set the mark on eol', function()
   1456    set_extmark(ns, marks[1], 0, -1)
   1457    local rv = get_extmark_by_id(ns, marks[1])
   1458    eq({ 0, init_text:len() }, rv)
   1459    -- Test another
   1460    set_extmark(ns, marks[1], 0, -1)
   1461    rv = get_extmark_by_id(ns, marks[1])
   1462    eq({ 0, init_text:len() }, rv)
   1463  end)
   1464 
   1465  it('when col = line-length, set the mark on eol', function()
   1466    local invalid_col = init_text:len() + 1
   1467    eq("Invalid 'col': out of range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
   1468  end)
   1469 
   1470  it('fails when line > line_count', function()
   1471    local invalid_col = init_text:len() + 1
   1472    local invalid_lnum = 3
   1473    eq(
   1474      "Invalid 'line': out of range",
   1475      pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)
   1476    )
   1477    eq({}, get_extmark_by_id(ns, marks[1]))
   1478  end)
   1479 
   1480  it('bug from check_col in extmark_set', function()
   1481    -- This bug was caused by extmark_set always using check_col. check_col
   1482    -- always uses the current buffer. This wasn't working during undo so we
   1483    -- now use check_col and check_lnum only when they are required.
   1484    feed('A<cr>67890<cr>xx<esc>')
   1485    feed('A<cr>12345<cr>67890<cr>xx<esc>')
   1486    set_extmark(ns, marks[1], 3, 4)
   1487    feed([[:1,5s:5\n:5 <cr>]])
   1488    check_undo_redo(ns, marks[1], 3, 4, 2, 6)
   1489  end)
   1490 
   1491  it('in read-only buffer', function()
   1492    command('view! runtime/doc/help.txt')
   1493    eq(true, api.nvim_get_option_value('ro', {}))
   1494    local id = set_extmark(ns, 0, 0, 2)
   1495    eq({ { id, 0, 2 } }, get_extmarks(ns, 0, -1))
   1496  end)
   1497 
   1498  it('can set a mark to other buffer', function()
   1499    local buf = request('nvim_create_buf', 0, 1)
   1500    request('nvim_buf_set_lines', buf, 0, -1, 1, { '', '' })
   1501    local id = api.nvim_buf_set_extmark(buf, ns, 1, 0, {})
   1502    eq({ { id, 1, 0 } }, api.nvim_buf_get_extmarks(buf, ns, 0, -1, {}))
   1503  end)
   1504 
   1505  it('does not crash with append/delete/undo sequence', function()
   1506    exec([[
   1507      let ns = nvim_create_namespace('myplugin')
   1508      call nvim_buf_set_extmark(0, ns, 0, 0, {})
   1509      call append(0, '')
   1510      %delete
   1511      undo]])
   1512    assert_alive()
   1513  end)
   1514 
   1515  it('works with left and right gravity', function()
   1516    -- right gravity should move with inserted text, while
   1517    -- left gravity should stay in place.
   1518    api.nvim_buf_set_extmark(0, ns, 0, 5, { right_gravity = false })
   1519    api.nvim_buf_set_extmark(0, ns, 0, 5, { right_gravity = true })
   1520    feed([[Aasdfasdf]])
   1521 
   1522    eq({ { 1, 0, 5 }, { 2, 0, 13 } }, api.nvim_buf_get_extmarks(0, ns, 0, -1, {}))
   1523 
   1524    -- but both move when text is inserted before
   1525    feed([[<esc>Iasdf<esc>]])
   1526    -- eq({}, api.nvim_buf_get_lines(0, 0, -1, true))
   1527    eq({ { 1, 0, 9 }, { 2, 0, 17 } }, api.nvim_buf_get_extmarks(0, ns, 0, -1, {}))
   1528 
   1529    -- clear text
   1530    api.nvim_buf_set_text(0, 0, 0, 0, 17, {})
   1531 
   1532    -- handles set_text correctly as well
   1533    eq({ { 1, 0, 0 }, { 2, 0, 0 } }, api.nvim_buf_get_extmarks(0, ns, 0, -1, {}))
   1534    api.nvim_buf_set_text(0, 0, 0, 0, 0, { 'asdfasdf' })
   1535    eq({ { 1, 0, 0 }, { 2, 0, 8 } }, api.nvim_buf_get_extmarks(0, ns, 0, -1, {}))
   1536 
   1537    feed('u')
   1538    -- handles pasting
   1539    exec([[let @a='asdfasdf']])
   1540    feed([["ap]])
   1541    eq({ { 1, 0, 0 }, { 2, 0, 8 } }, api.nvim_buf_get_extmarks(0, ns, 0, -1, {}))
   1542  end)
   1543 
   1544  it('can accept "end_row" or "end_line" #16548', function()
   1545    set_extmark(ns, marks[1], 0, 0, {
   1546      end_col = 0,
   1547      end_line = 1,
   1548    })
   1549    eq({
   1550      {
   1551        1,
   1552        0,
   1553        0,
   1554        {
   1555          ns_id = ns,
   1556          end_col = 0,
   1557          end_row = 1,
   1558          right_gravity = true,
   1559          end_right_gravity = false,
   1560        },
   1561      },
   1562    }, get_extmarks(ns, 0, -1, { details = true }))
   1563  end)
   1564 
   1565  it('in prompt buffer', function()
   1566    feed('dd')
   1567    set_extmark(ns, marks[1], 0, 0, {})
   1568    api.nvim_set_option_value('buftype', 'prompt', {})
   1569    feed('i<esc>')
   1570    eq({ { marks[1], 0, 2 } }, get_extmarks(ns, 0, -1))
   1571    fn.prompt_setprompt('', 'foo > ')
   1572    eq({ { marks[1], 0, 6 } }, get_extmarks(ns, 0, -1))
   1573    feed('ihello')
   1574    eq({ { marks[1], 0, 11 } }, get_extmarks(ns, 0, -1))
   1575 
   1576    local function get_extmark_range(id)
   1577      local rv = get_extmark_by_id(ns, id, { details = true })
   1578      return rv[3].invalid and 'invalid' or { rv[1], rv[2], rv[3].end_row, rv[3].end_col }
   1579    end
   1580 
   1581    set_extmark(ns, marks[2], 0, 0, { invalidate = true, end_col = 6 })
   1582    set_extmark(ns, marks[3], 0, 6, { invalidate = true, end_col = 11 })
   1583    set_extmark(ns, marks[4], 0, 0, { invalidate = true, end_col = 11 })
   1584    set_extmark(ns, marks[5], 0, 0, { invalidate = true, end_row = 1 })
   1585    fn.prompt_setprompt('', 'floob > ')
   1586    eq({ 0, 13 }, get_extmark_range(marks[1]))
   1587    eq('invalid', get_extmark_range(marks[2])) -- extmark spanning old prompt invalidated
   1588    eq({ 0, 8, 0, 13 }, get_extmark_range(marks[3]))
   1589    eq({ 0, 8, 0, 13 }, get_extmark_range(marks[4]))
   1590    eq({ 0, 8, 1, 0 }, get_extmark_range(marks[5]))
   1591 
   1592    set_extmark(ns, marks[2], 0, 0, { invalidate = true, end_col = 8 })
   1593    set_extmark(ns, marks[3], 0, 8, { invalidate = true, end_col = 13 })
   1594    set_extmark(ns, marks[4], 0, 0, { invalidate = true, end_col = 13 })
   1595    set_extmark(ns, marks[5], 0, 0, { invalidate = true, end_row = 1 })
   1596    -- Do this in the same event.
   1597    exec_lua(function()
   1598      vim.fn.setpos("':", { 0, 1, 999, 0 })
   1599      vim.fn.prompt_setprompt('', 'discard > ')
   1600    end)
   1601    eq({ 0, 10 }, get_extmark_range(marks[1]))
   1602    eq('invalid', get_extmark_range(marks[2])) -- all spans on line invalidated
   1603    eq('invalid', get_extmark_range(marks[3]))
   1604    eq('invalid', get_extmark_range(marks[4]))
   1605    eq({ 0, 10, 1, 0 }, get_extmark_range(marks[5]))
   1606 
   1607    feed('hello')
   1608    eq({ 0, 15 }, get_extmark_range(marks[1]))
   1609    eq({ 0, 15, 1, 0 }, get_extmark_range(marks[5]))
   1610    -- init_prompt uses correct range for inserted_bytes when fixing empty prompt.
   1611    fn.setline('.', { '', 'last line' })
   1612    eq({ 'discard > ', 'last line' }, api.nvim_buf_get_lines(0, 0, -1, true))
   1613    eq({ 0, 10 }, get_extmark_range(marks[1]))
   1614    eq({ 0, 10, 1, 0 }, get_extmark_range(marks[5]))
   1615  end)
   1616 
   1617  it('can get details', function()
   1618    set_extmark(ns, marks[1], 0, 0, {
   1619      conceal = 'c',
   1620      conceal_lines = '',
   1621      cursorline_hl_group = 'Statement',
   1622      end_col = 0,
   1623      end_right_gravity = true,
   1624      end_row = 1,
   1625      hl_eol = true,
   1626      hl_group = 'String',
   1627      hl_mode = 'blend',
   1628      line_hl_group = 'Statement',
   1629      number_hl_group = 'Statement',
   1630      priority = 0,
   1631      right_gravity = false,
   1632      sign_hl_group = 'Statement',
   1633      sign_text = '>>',
   1634      spell = true,
   1635      virt_lines = {
   1636        { { 'lines', 'Macro' }, { '???' }, { ';;;', '' } },
   1637        { { 'stack', { 'Type', 'Search' } }, { '!!!' } },
   1638      },
   1639      virt_lines_above = true,
   1640      virt_lines_leftcol = true,
   1641      virt_text = { { 'text', 'Macro' }, { '???' }, { 'stack', { 'Type', 'Search' } } },
   1642      virt_text_hide = true,
   1643      virt_text_pos = 'right_align',
   1644    })
   1645    eq({
   1646      0,
   1647      0,
   1648      {
   1649        conceal = 'c',
   1650        conceal_lines = '',
   1651        cursorline_hl_group = 'Statement',
   1652        end_col = 0,
   1653        end_right_gravity = true,
   1654        end_row = 1,
   1655        hl_eol = true,
   1656        hl_group = 'String',
   1657        hl_mode = 'blend',
   1658        line_hl_group = 'Statement',
   1659        ns_id = ns,
   1660        number_hl_group = 'Statement',
   1661        priority = 0,
   1662        right_gravity = false,
   1663        sign_hl_group = 'Statement',
   1664        sign_text = '>>',
   1665        spell = true,
   1666        virt_lines = {
   1667          { { 'lines', 'Macro' }, { '???' }, { ';;;', '' } },
   1668          { { 'stack', { 'Type', 'Search' } }, { '!!!' } },
   1669        },
   1670        virt_lines_above = true,
   1671        virt_lines_leftcol = true,
   1672        virt_lines_overflow = 'trunc',
   1673        virt_text = { { 'text', 'Macro' }, { '???' }, { 'stack', { 'Type', 'Search' } } },
   1674        virt_text_repeat_linebreak = false,
   1675        virt_text_hide = true,
   1676        virt_text_pos = 'right_align',
   1677      },
   1678    }, get_extmark_by_id(ns, marks[1], { details = true }))
   1679 
   1680    set_extmark(ns, marks[2], 0, 0, {
   1681      priority = 0,
   1682      virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } },
   1683      virt_text_repeat_linebreak = true,
   1684      virt_text_win_col = 1,
   1685    })
   1686    eq({
   1687      0,
   1688      0,
   1689      {
   1690        ns_id = ns,
   1691        right_gravity = true,
   1692        priority = 0,
   1693        virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } },
   1694        virt_text_repeat_linebreak = true,
   1695        virt_text_hide = false,
   1696        virt_text_pos = 'win_col',
   1697        virt_text_win_col = 1,
   1698      },
   1699    }, get_extmark_by_id(ns, marks[2], { details = true }))
   1700 
   1701    set_extmark(ns, marks[3], 0, 0, {
   1702      priority = 0,
   1703      ui_watched = true,
   1704      virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
   1705      virt_lines_overflow = 'scroll',
   1706    })
   1707    eq({
   1708      0,
   1709      0,
   1710      {
   1711        ns_id = ns,
   1712        right_gravity = true,
   1713        ui_watched = true,
   1714        priority = 0,
   1715        virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
   1716        virt_lines_above = false,
   1717        virt_lines_leftcol = false,
   1718        virt_lines_overflow = 'scroll',
   1719      },
   1720    }, get_extmark_by_id(ns, marks[3], { details = true }))
   1721 
   1722    set_extmark(ns, marks[4], 0, 0, { cursorline_hl_group = 'Statement' })
   1723    eq({
   1724      0,
   1725      0,
   1726      {
   1727        ns_id = ns,
   1728        cursorline_hl_group = 'Statement',
   1729        priority = 4096,
   1730        right_gravity = true,
   1731      },
   1732    }, get_extmark_by_id(ns, marks[4], { details = true }))
   1733 
   1734    set_extmark(ns, marks[5], 0, 0, {
   1735      end_col = 1,
   1736      conceal = 'a',
   1737      spell = true,
   1738    })
   1739    eq({
   1740      0,
   1741      0,
   1742      {
   1743        conceal = 'a',
   1744        end_col = 1,
   1745        end_right_gravity = false,
   1746        end_row = 0,
   1747        ns_id = ns,
   1748        right_gravity = true,
   1749        spell = true,
   1750      },
   1751    }, get_extmark_by_id(ns, marks[5], { details = true }))
   1752 
   1753    set_extmark(ns, marks[6], 0, 0, {
   1754      end_col = 1,
   1755      spell = false,
   1756    })
   1757    eq({
   1758      0,
   1759      0,
   1760      {
   1761        end_col = 1,
   1762        end_right_gravity = false,
   1763        end_row = 0,
   1764        ns_id = ns,
   1765        right_gravity = true,
   1766        spell = false,
   1767      },
   1768    }, get_extmark_by_id(ns, marks[6], { details = true }))
   1769 
   1770    api.nvim_buf_clear_namespace(0, ns, 0, -1)
   1771    -- legacy sign mark includes sign name
   1772    command('sign define sign1 text=s1 texthl=Title linehl=LineNR numhl=Normal culhl=CursorLine')
   1773    command('sign place 1 name=sign1 line=1')
   1774    eq({
   1775      {
   1776        1,
   1777        0,
   1778        0,
   1779        {
   1780          cursorline_hl_group = 'CursorLine',
   1781          invalidate = true,
   1782          line_hl_group = 'LineNr',
   1783          ns_id = 0,
   1784          number_hl_group = 'Normal',
   1785          priority = 10,
   1786          right_gravity = true,
   1787          sign_hl_group = 'Title',
   1788          sign_name = 'sign1',
   1789          sign_text = 's1',
   1790          undo_restore = false,
   1791        },
   1792      },
   1793    }, get_extmarks(-1, 0, -1, { details = true }))
   1794  end)
   1795 
   1796  it('can get marks from anonymous namespaces', function()
   1797    ns = request('nvim_create_namespace', '')
   1798    ns2 = request('nvim_create_namespace', '')
   1799    set_extmark(ns, 1, 0, 0, {})
   1800    set_extmark(ns2, 2, 1, 0, {})
   1801    eq({
   1802      { 1, 0, 0, { ns_id = ns, right_gravity = true } },
   1803      { 2, 1, 0, { ns_id = ns2, right_gravity = true } },
   1804    }, get_extmarks(-1, 0, -1, { details = true }))
   1805  end)
   1806 
   1807  it('can filter by extmark properties', function()
   1808    set_extmark(ns, 1, 0, 0, {})
   1809    set_extmark(ns, 2, 0, 0, { hl_group = 'Normal' })
   1810    set_extmark(ns, 3, 0, 0, { sign_text = '>>' })
   1811    set_extmark(ns, 4, 0, 0, { virt_text = { { 'text', 'Normal' } } })
   1812    set_extmark(ns, 5, 0, 0, { virt_lines = { { { 'line', 'Normal' } } } })
   1813    eq(5, #get_extmarks(-1, 0, -1, {}))
   1814    eq({ { 2, 0, 0 } }, get_extmarks(-1, 0, -1, { type = 'highlight' }))
   1815    eq({ { 3, 0, 0 } }, get_extmarks(-1, 0, -1, { type = 'sign' }))
   1816    eq({ { 4, 0, 0 } }, get_extmarks(-1, 0, -1, { type = 'virt_text' }))
   1817    eq({ { 5, 0, 0 } }, get_extmarks(-1, 0, -1, { type = 'virt_lines' }))
   1818  end)
   1819 
   1820  it('invalidated marks are deleted', function()
   1821    screen = Screen.new(40, 6)
   1822    feed('dd6iaaa bbb ccc<CR><ESC>gg')
   1823    api.nvim_set_option_value('signcolumn', 'auto:3', {})
   1824    set_extmark(ns, 1, 0, 0, { invalidate = true, sign_text = 'S1', end_row = 1 })
   1825    set_extmark(ns, 2, 1, 0, { invalidate = true, sign_text = 'S2', end_row = 2, end_col = 0 })
   1826    set_extmark(ns, 3, 1, 0, { invalidate = true, sign_text = 'S3', end_row = 2, end_col = 1 })
   1827    -- mark with invalidate is removed
   1828    command('d2')
   1829    screen:expect([[
   1830      {7:S3}^aaa bbb ccc                           |
   1831      {7:  }aaa bbb ccc                           |*3
   1832      {7:  }                                      |
   1833                                              |
   1834    ]])
   1835    -- mark is restored with undo_restore == true
   1836    command('silent undo')
   1837    screen:expect([[
   1838      {7:S1    }^aaa bbb ccc                       |
   1839      {7:S3S2S1}aaa bbb ccc                       |
   1840      {7:S3S2  }aaa bbb ccc                       |
   1841      {7:      }aaa bbb ccc                       |*2
   1842                                              |
   1843    ]])
   1844    -- decor is not removed twice
   1845    command('d3')
   1846    api.nvim_buf_del_extmark(0, ns, 1)
   1847    api.nvim_buf_del_extmark(0, ns, 3)
   1848    command('silent undo')
   1849    -- mark is deleted with undo_restore == false
   1850    set_extmark(ns, 1, 0, 0, { invalidate = true, undo_restore = false, sign_text = 'S1' })
   1851    set_extmark(ns, 2, 1, 0, { invalidate = true, undo_restore = false, sign_text = 'S2' })
   1852    command('1d 2')
   1853    eq(0, #get_extmarks(-1, 0, -1, {}))
   1854    -- mark is not removed when deleting bytes before the range
   1855    set_extmark(ns, 3, 0, 4, {
   1856      invalidate = true,
   1857      hl_group = 'Error',
   1858      end_col = 7,
   1859      right_gravity = false,
   1860    })
   1861    feed('dw')
   1862    eq(3, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
   1863    -- mark is not removed when deleting bytes at the start of the range
   1864    feed('x')
   1865    eq(2, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
   1866    -- mark is not removed when deleting bytes from the end of the range
   1867    feed('lx')
   1868    eq(1, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
   1869    -- mark is not removed when deleting bytes beyond end of the range
   1870    feed('x')
   1871    eq(1, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
   1872    -- mark is removed when all bytes in the range are deleted
   1873    feed('hx')
   1874    eq(true, get_extmark_by_id(ns, 3, { details = true })[3].invalid)
   1875    -- mark is restored with undo_restore == true if pos did not change
   1876    command('undo')
   1877    eq(nil, get_extmark_by_id(ns, 3, { details = true })[3].invalid)
   1878    -- multiline mark is not removed when start of its range is deleted
   1879    set_extmark(ns, 4, 1, 4, {
   1880      undo_restore = false,
   1881      invalidate = true,
   1882      hl_group = 'Error',
   1883      end_col = 7,
   1884      end_row = 3,
   1885    })
   1886    feed('ddDdd')
   1887    eq({ 0, 0 }, get_extmark_by_id(ns, 4, {}))
   1888    -- multiline mark is removed when entirety of its range is deleted
   1889    feed('vj2ed')
   1890    eq({}, get_extmark_by_id(ns, 4, {}))
   1891  end)
   1892 
   1893  it('no crash checking invalidated flag of sign pair end key #31856', function()
   1894    api.nvim_buf_set_lines(0, 0, 1, false, { '', '' })
   1895    api.nvim_set_option_value('signcolumn', 'auto:2', {})
   1896    set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 })
   1897    set_extmark(ns, 2, 1, 0, { sign_text = 'S2', end_row = 1 })
   1898    command('d')
   1899    api.nvim_buf_clear_namespace(0, ns, 0, -1)
   1900    n.assert_alive()
   1901  end)
   1902 
   1903  it('can set a URL', function()
   1904    local url1 = 'https://example.com'
   1905    local url2 = 'http://127.0.0.1'
   1906    set_extmark(ns, 1, 0, 0, { url = url1, end_col = 3 })
   1907    set_extmark(ns, 2, 0, 3, { url = url2, hl_group = 'Search', end_col = 5 })
   1908    local extmarks = get_extmarks(ns, 0, -1, { details = true })
   1909    eq(2, #extmarks)
   1910    eq(url1, extmarks[1][4].url)
   1911    eq(url2, extmarks[2][4].url)
   1912    eq('Search', extmarks[2][4].hl_group)
   1913  end)
   1914 
   1915  it('respects priority', function()
   1916    screen = Screen.new(15, 10)
   1917 
   1918    set_extmark(ns, marks[1], 0, 0, {
   1919      hl_group = 'Comment',
   1920      end_col = 2,
   1921      priority = 20,
   1922    })
   1923 
   1924    -- Extmark defined after first extmark but has lower priority, first extmark "wins"
   1925    set_extmark(ns, marks[2], 0, 0, {
   1926      hl_group = 'String',
   1927      end_col = 2,
   1928      priority = 10,
   1929    })
   1930 
   1931    screen:expect {
   1932      grid = [[
   1933      {1:12}34^5          |
   1934      {2:~              }|*8
   1935                     |
   1936    ]],
   1937      attr_ids = {
   1938        [1] = { foreground = Screen.colors.Blue1 },
   1939        [2] = { foreground = Screen.colors.Blue1, bold = true },
   1940      },
   1941    }
   1942  end)
   1943 end)
   1944 
   1945 describe('Extmarks buffer api with many marks', function()
   1946  local ns1
   1947  local ns2
   1948  local ns_marks = {}
   1949  before_each(function()
   1950    clear()
   1951    ns1 = request('nvim_create_namespace', 'ns1')
   1952    ns2 = request('nvim_create_namespace', 'ns2')
   1953    ns_marks = { [ns1] = {}, [ns2] = {} }
   1954    local lines = {}
   1955    for i = 1, 30 do
   1956      lines[#lines + 1] = string.rep('x ', i)
   1957    end
   1958    api.nvim_buf_set_lines(0, 0, -1, true, lines)
   1959    local ns = ns1
   1960    local q = 0
   1961    for i = 0, 29 do
   1962      for j = 0, i do
   1963        local id = set_extmark(ns, 0, i, j)
   1964        eq(nil, ns_marks[ns][id])
   1965        ok(id > 0)
   1966        ns_marks[ns][id] = { i, j }
   1967        ns = ns1 + ns2 - ns
   1968        q = q + 1
   1969      end
   1970    end
   1971    eq(233, #ns_marks[ns1])
   1972    eq(232, #ns_marks[ns2])
   1973  end)
   1974 
   1975  local function get_marks(ns)
   1976    local mark_list = get_extmarks(ns, 0, -1)
   1977    local marks = {}
   1978    for _, mark in ipairs(mark_list) do
   1979      local id, row, col = unpack(mark)
   1980      eq(nil, marks[id], 'duplicate mark')
   1981      marks[id] = { row, col }
   1982    end
   1983    return marks
   1984  end
   1985 
   1986  it('can get marks', function()
   1987    eq(ns_marks[ns1], get_marks(ns1))
   1988    eq(ns_marks[ns2], get_marks(ns2))
   1989  end)
   1990 
   1991  it('can clear all marks in ns', function()
   1992    api.nvim_buf_clear_namespace(0, ns1, 0, -1)
   1993    eq({}, get_marks(ns1))
   1994    eq(ns_marks[ns2], get_marks(ns2))
   1995    api.nvim_buf_clear_namespace(0, ns2, 0, -1)
   1996    eq({}, get_marks(ns1))
   1997    eq({}, get_marks(ns2))
   1998  end)
   1999 
   2000  it('can clear line range', function()
   2001    api.nvim_buf_clear_namespace(0, ns1, 10, 20)
   2002    for id, mark in pairs(ns_marks[ns1]) do
   2003      if 10 <= mark[1] and mark[1] < 20 then
   2004        ns_marks[ns1][id] = nil
   2005      end
   2006    end
   2007    eq(ns_marks[ns1], get_marks(ns1))
   2008    eq(ns_marks[ns2], get_marks(ns2))
   2009 
   2010    api.nvim_buf_clear_namespace(0, ns1, 0, 10)
   2011    for id, mark in pairs(ns_marks[ns1]) do
   2012      if mark[1] < 10 then
   2013        ns_marks[ns1][id] = nil
   2014      end
   2015    end
   2016    eq(ns_marks[ns1], get_marks(ns1))
   2017    eq(ns_marks[ns2], get_marks(ns2))
   2018 
   2019    api.nvim_buf_clear_namespace(0, ns1, 20, -1)
   2020    for id, mark in pairs(ns_marks[ns1]) do
   2021      if mark[1] >= 20 then
   2022        ns_marks[ns1][id] = nil
   2023      end
   2024    end
   2025    eq(ns_marks[ns1], get_marks(ns1))
   2026    eq(ns_marks[ns2], get_marks(ns2))
   2027  end)
   2028 
   2029  it('can delete line', function()
   2030    feed('10Gdd')
   2031    for _, marks in pairs(ns_marks) do
   2032      for id, mark in pairs(marks) do
   2033        if mark[1] == 9 then
   2034          marks[id] = { 9, 0 }
   2035        elseif mark[1] >= 10 then
   2036          mark[1] = mark[1] - 1
   2037        end
   2038      end
   2039    end
   2040    eq(ns_marks[ns1], get_marks(ns1))
   2041    eq(ns_marks[ns2], get_marks(ns2))
   2042  end)
   2043 
   2044  it('can delete lines', function()
   2045    feed('10G10dd')
   2046    for _, marks in pairs(ns_marks) do
   2047      for id, mark in pairs(marks) do
   2048        if 9 <= mark[1] and mark[1] < 19 then
   2049          marks[id] = { 9, 0 }
   2050        elseif mark[1] >= 19 then
   2051          mark[1] = mark[1] - 10
   2052        end
   2053      end
   2054    end
   2055    eq(ns_marks[ns1], get_marks(ns1))
   2056    eq(ns_marks[ns2], get_marks(ns2))
   2057  end)
   2058 
   2059  it('can wipe buffer', function()
   2060    command('bwipe!')
   2061    eq({}, get_marks(ns1))
   2062    eq({}, get_marks(ns2))
   2063  end)
   2064 end)
   2065 
   2066 describe('API/win_extmark', function()
   2067  local screen
   2068  local marks, line1, line2
   2069  local ns
   2070 
   2071  before_each(function()
   2072    -- Initialize some namespaces and insert text into a buffer
   2073    marks = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }
   2074 
   2075    line1 = 'non ui-watched line'
   2076    line2 = 'ui-watched line'
   2077 
   2078    clear()
   2079 
   2080    insert(line1)
   2081    feed('o<esc>')
   2082    insert(line2)
   2083    ns = request('nvim_create_namespace', 'extmark-ui')
   2084  end)
   2085 
   2086  it('sends and only sends ui-watched marks to ui', function()
   2087    screen = Screen.new(20, 4)
   2088    -- should send this
   2089    set_extmark(ns, marks[1], 1, 0, { ui_watched = true })
   2090    -- should not send this
   2091    set_extmark(ns, marks[2], 0, 0, { ui_watched = false })
   2092    screen:expect({
   2093      grid = [[
   2094      non ui-watched line |
   2095      ui-watched lin^e     |
   2096      {1:~                   }|
   2097                          |
   2098    ]],
   2099      extmarks = {
   2100        [2] = {
   2101          -- positioned at the end of the 2nd line
   2102          { 1000, ns, marks[1], 1, 16 },
   2103        },
   2104      },
   2105    })
   2106  end)
   2107 
   2108  it('sends multiple ui-watched marks to ui', function()
   2109    screen = Screen.new(20, 4)
   2110    feed('15A!<Esc>')
   2111    -- should send all of these
   2112    set_extmark(ns, marks[1], 1, 0, { ui_watched = true, virt_text_pos = 'overlay' })
   2113    set_extmark(ns, marks[2], 1, 2, { ui_watched = true, virt_text_pos = 'overlay' })
   2114    set_extmark(ns, marks[3], 1, 4, { ui_watched = true, virt_text_pos = 'overlay' })
   2115    set_extmark(ns, marks[4], 1, 6, { ui_watched = true, virt_text_pos = 'overlay' })
   2116    set_extmark(ns, marks[5], 1, 8, { ui_watched = true })
   2117    screen:expect({
   2118      grid = [[
   2119      non ui-watched line |
   2120      ui-watched line!!!!!|
   2121      !!!!!!!!!^!          |
   2122                          |
   2123    ]],
   2124      extmarks = {
   2125        [2] = {
   2126          -- notification from 1st call
   2127          { 1000, ns, marks[1], 1, 0 },
   2128          -- notifications from 2nd call
   2129          { 1000, ns, marks[1], 1, 0 },
   2130          { 1000, ns, marks[2], 1, 2 },
   2131          -- notifications from 3rd call
   2132          { 1000, ns, marks[1], 1, 0 },
   2133          { 1000, ns, marks[2], 1, 2 },
   2134          { 1000, ns, marks[3], 1, 4 },
   2135          -- notifications from 4th call
   2136          { 1000, ns, marks[1], 1, 0 },
   2137          { 1000, ns, marks[2], 1, 2 },
   2138          { 1000, ns, marks[3], 1, 4 },
   2139          { 1000, ns, marks[4], 1, 6 },
   2140          -- final
   2141          --   overlay
   2142          { 1000, ns, marks[1], 1, 0 },
   2143          { 1000, ns, marks[2], 1, 2 },
   2144          { 1000, ns, marks[3], 1, 4 },
   2145          { 1000, ns, marks[4], 1, 6 },
   2146          --   eol
   2147          { 1000, ns, marks[5], 2, 11 },
   2148        },
   2149      },
   2150    })
   2151  end)
   2152 
   2153  it('updates ui-watched marks', function()
   2154    screen = Screen.new(20, 4)
   2155    -- should send this
   2156    set_extmark(ns, marks[1], 1, 0, { ui_watched = true })
   2157    -- should not send this
   2158    set_extmark(ns, marks[2], 0, 0, { ui_watched = false })
   2159    -- make some changes
   2160    insert(' update')
   2161    screen:expect({
   2162      grid = [[
   2163      non ui-watched line |
   2164      ui-watched linupdat^e|
   2165      e                   |
   2166                          |
   2167    ]],
   2168      extmarks = {
   2169        [2] = {
   2170          -- positioned at the end of the 2nd line
   2171          { 1000, ns, marks[1], 1, 16 },
   2172          -- updated and wrapped to 3rd line
   2173          { 1000, ns, marks[1], 2, 2 },
   2174        },
   2175      },
   2176    })
   2177    feed('<c-e>')
   2178    screen:expect({
   2179      grid = [[
   2180      ui-watched linupdat^e|
   2181      e                   |
   2182      {1:~                   }|
   2183                          |
   2184    ]],
   2185      extmarks = {
   2186        [2] = {
   2187          -- positioned at the end of the 2nd line
   2188          { 1000, ns, marks[1], 1, 16 },
   2189          -- updated and wrapped to 3rd line
   2190          { 1000, ns, marks[1], 2, 2 },
   2191          -- scrolled up one line, should be handled by grid scroll
   2192        },
   2193      },
   2194    })
   2195  end)
   2196 
   2197  it('sends ui-watched to splits', function()
   2198    screen = Screen.new(20, 8, { ext_multigrid = true })
   2199    -- should send this
   2200    set_extmark(ns, marks[1], 1, 0, { ui_watched = true })
   2201    -- should not send this
   2202    set_extmark(ns, marks[2], 0, 0, { ui_watched = false })
   2203    command('split')
   2204    screen:expect({
   2205      grid = [[
   2206        ## grid 1
   2207          [4:--------------------]|*3
   2208          {3:[No Name] [+]       }|
   2209          [2:--------------------]|*2
   2210          {2:[No Name] [+]       }|
   2211          [3:--------------------]|
   2212        ## grid 2
   2213          non ui-watched line |
   2214          ui-watched line     |
   2215        ## grid 3
   2216                              |
   2217        ## grid 4
   2218          non ui-watched line |
   2219          ui-watched lin^e     |
   2220          {1:~                   }|
   2221    ]],
   2222      extmarks = {
   2223        [2] = {
   2224          -- positioned at the end of the 2nd line
   2225          { 1000, ns, marks[1], 1, 16 },
   2226          -- updated after split
   2227          { 1000, ns, marks[1], 1, 16 },
   2228        },
   2229        [4] = {
   2230          -- only after split
   2231          { 1001, ns, marks[1], 1, 16 },
   2232        },
   2233      },
   2234    })
   2235    -- make some changes
   2236    insert(' update')
   2237    screen:expect({
   2238      grid = [[
   2239        ## grid 1
   2240          [4:--------------------]|*3
   2241          {3:[No Name] [+]       }|
   2242          [2:--------------------]|*2
   2243          {2:[No Name] [+]       }|
   2244          [3:--------------------]|
   2245        ## grid 2
   2246          non ui-watched line |
   2247          ui-watched linupd{1:@@@}|
   2248        ## grid 3
   2249                              |
   2250        ## grid 4
   2251          non ui-watched line |
   2252          ui-watched linupdat^e|
   2253          e                   |
   2254    ]],
   2255      extmarks = {
   2256        [2] = {
   2257          -- positioned at the end of the 2nd line
   2258          { 1000, ns, marks[1], 1, 16 },
   2259          -- updated after split
   2260          { 1000, ns, marks[1], 1, 16 },
   2261        },
   2262        [4] = {
   2263          { 1001, ns, marks[1], 1, 16 },
   2264          -- updated
   2265          { 1001, ns, marks[1], 2, 2 },
   2266        },
   2267      },
   2268    })
   2269  end)
   2270 end)