neovim

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

buffer_spec.lua (83745B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local clear = n.clear
      6 local eq = t.eq
      7 local ok = t.ok
      8 local describe_lua_and_rpc = n.describe_lua_and_rpc(describe)
      9 local api = n.api
     10 local fn = n.fn
     11 local request = n.request
     12 local exec_lua = n.exec_lua
     13 local insert = n.insert
     14 local NIL = vim.NIL
     15 local command = n.command
     16 local feed = n.feed
     17 local pcall_err = t.pcall_err
     18 local assert_alive = n.assert_alive
     19 
     20 describe('api/buf', function()
     21  before_each(clear)
     22 
     23  -- access deprecated functions
     24  local function curbuf_depr(method, ...)
     25    return request('buffer_' .. method, 0, ...)
     26  end
     27 
     28  describe('nvim_buf_set_lines, nvim_buf_line_count', function()
     29    it('deprecated forms', function()
     30      eq(1, curbuf_depr('line_count'))
     31      curbuf_depr('insert', -1, { 'line' })
     32      eq(2, curbuf_depr('line_count'))
     33      curbuf_depr('insert', -1, { 'line' })
     34      eq(3, curbuf_depr('line_count'))
     35      curbuf_depr('del_line', -1)
     36      eq(2, curbuf_depr('line_count'))
     37      curbuf_depr('del_line', -1)
     38      curbuf_depr('del_line', -1)
     39      -- There's always at least one line
     40      eq(1, curbuf_depr('line_count'))
     41    end)
     42 
     43    it("doesn't crash just after set undolevels=1 #24894", function()
     44      local buf = api.nvim_create_buf(false, true)
     45      api.nvim_set_option_value('undolevels', -1, { buf = buf })
     46      api.nvim_buf_set_lines(buf, 0, 1, false, {})
     47 
     48      assert_alive()
     49    end)
     50 
     51    it('cursor position is maintained after lines are inserted #9961', function()
     52      -- replace the buffer contents with these three lines.
     53      api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
     54      -- Set the current cursor to {3, 2}.
     55      api.nvim_win_set_cursor(0, { 3, 2 })
     56 
     57      -- add 2 lines and delete 1 line above the current cursor position.
     58      api.nvim_buf_set_lines(0, 1, 2, true, { 'line5', 'line6' })
     59      -- check the current set of lines in the buffer.
     60      eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(0, 0, -1, true))
     61      -- cursor should be moved below by 1 line.
     62      eq({ 4, 2 }, api.nvim_win_get_cursor(0))
     63 
     64      -- add a line after the current cursor position.
     65      api.nvim_buf_set_lines(0, 5, 5, true, { 'line7' })
     66      -- check the current set of lines in the buffer.
     67      eq(
     68        { 'line1', 'line5', 'line6', 'line3', 'line4', 'line7' },
     69        api.nvim_buf_get_lines(0, 0, -1, true)
     70      )
     71      -- cursor position is unchanged.
     72      eq({ 4, 2 }, api.nvim_win_get_cursor(0))
     73 
     74      -- overwrite current cursor line.
     75      api.nvim_buf_set_lines(0, 3, 5, true, { 'line8', 'line9' })
     76      -- check the current set of lines in the buffer.
     77      eq(
     78        { 'line1', 'line5', 'line6', 'line8', 'line9', 'line7' },
     79        api.nvim_buf_get_lines(0, 0, -1, true)
     80      )
     81      -- cursor position is unchanged.
     82      eq({ 4, 2 }, api.nvim_win_get_cursor(0))
     83 
     84      -- delete current cursor line.
     85      api.nvim_buf_set_lines(0, 3, 5, true, {})
     86      -- check the current set of lines in the buffer.
     87      eq({ 'line1', 'line5', 'line6', 'line7' }, api.nvim_buf_get_lines(0, 0, -1, true))
     88      -- cursor position is unchanged.
     89      eq({ 4, 2 }, api.nvim_win_get_cursor(0))
     90    end)
     91 
     92    it('cursor position is maintained in non-current window', function()
     93      api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
     94      api.nvim_win_set_cursor(0, { 3, 2 })
     95      local win = api.nvim_get_current_win()
     96      local buf = api.nvim_get_current_buf()
     97 
     98      command('new')
     99 
    100      api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' })
    101      eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true))
    102      eq({ 4, 2 }, api.nvim_win_get_cursor(win))
    103    end)
    104 
    105    it('cursor position is maintained in TWO non-current windows', function()
    106      api.nvim_buf_set_lines(0, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
    107      api.nvim_win_set_cursor(0, { 3, 2 })
    108      local win = api.nvim_get_current_win()
    109      local buf = api.nvim_get_current_buf()
    110 
    111      command('split')
    112      api.nvim_win_set_cursor(0, { 4, 2 })
    113      local win2 = api.nvim_get_current_win()
    114 
    115      -- set current window to third one with another buffer
    116      command('new')
    117 
    118      api.nvim_buf_set_lines(buf, 1, 2, true, { 'line5', 'line6' })
    119      eq({ 'line1', 'line5', 'line6', 'line3', 'line4' }, api.nvim_buf_get_lines(buf, 0, -1, true))
    120      eq({ 4, 2 }, api.nvim_win_get_cursor(win))
    121      eq({ 5, 2 }, api.nvim_win_get_cursor(win2))
    122    end)
    123 
    124    it('cursor position is maintained consistently with viewport', function()
    125      local screen = Screen.new(20, 12)
    126 
    127      local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' }
    128      local buf = api.nvim_get_current_buf()
    129 
    130      api.nvim_buf_set_lines(buf, 0, -1, true, lines)
    131 
    132      command('6')
    133      command('new')
    134      screen:expect {
    135        grid = [[
    136        ^                    |
    137        {1:~                   }|*4
    138        {3:[No Name]           }|
    139        line5               |
    140        line6               |
    141        {1:~                   }|*2
    142        {2:[No Name] [+]       }|
    143                            |
    144      ]],
    145      }
    146 
    147      lines[5] = 'boogalo 5'
    148      api.nvim_buf_set_lines(buf, 0, -1, true, lines)
    149      screen:expect {
    150        grid = [[
    151        ^                    |
    152        {1:~                   }|*4
    153        {3:[No Name]           }|
    154        boogalo 5           |
    155        line6               |
    156        {1:~                   }|*2
    157        {2:[No Name] [+]       }|
    158                            |
    159      ]],
    160      }
    161 
    162      command('wincmd w')
    163      screen:expect {
    164        grid = [[
    165                            |
    166        {1:~                   }|*4
    167        {2:[No Name]           }|
    168        boogalo 5           |
    169        ^line6               |
    170        {1:~                   }|*2
    171        {3:[No Name] [+]       }|
    172                            |
    173      ]],
    174      }
    175    end)
    176 
    177    it('line_count has defined behaviour for unloaded buffers', function()
    178      -- we'll need to know our bufnr for when it gets unloaded
    179      local bufnr = api.nvim_get_current_buf()
    180      -- replace the buffer contents with these three lines
    181      api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
    182      -- check the line count is correct
    183      eq(4, api.nvim_buf_line_count(bufnr))
    184      -- force unload the buffer (this will discard changes)
    185      command('new')
    186      command('bunload! ' .. bufnr)
    187      -- line count for an unloaded buffer should always be 0
    188      eq(0, api.nvim_buf_line_count(bufnr))
    189    end)
    190 
    191    it('get_lines has defined behaviour for unloaded buffers', function()
    192      -- we'll need to know our bufnr for when it gets unloaded
    193      local bufnr = api.nvim_get_current_buf()
    194      -- replace the buffer contents with these three lines
    195      api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1', 'line2', 'line3', 'line4' })
    196      -- confirm that getting lines works
    197      eq({ 'line2', 'line3' }, api.nvim_buf_get_lines(bufnr, 1, 3, true))
    198      -- force unload the buffer (this will discard changes)
    199      command('new')
    200      command('bunload! ' .. bufnr)
    201      -- attempting to get lines now always gives empty list
    202      eq({}, api.nvim_buf_get_lines(bufnr, 1, 3, true))
    203      -- it's impossible to get out-of-bounds errors for an unloaded buffer
    204      eq({}, api.nvim_buf_get_lines(bufnr, 8888, 9999, true))
    205    end)
    206 
    207    describe('handles topline', function()
    208      local screen
    209      before_each(function()
    210        screen = Screen.new(20, 12)
    211        api.nvim_buf_set_lines(
    212          0,
    213          0,
    214          -1,
    215          true,
    216          { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' }
    217        )
    218        api.nvim_set_option_value('modified', false, {})
    219      end)
    220 
    221      it('of current window', function()
    222        local win = api.nvim_get_current_win()
    223        local buf = api.nvim_get_current_buf()
    224 
    225        command('new | wincmd w')
    226        api.nvim_win_set_cursor(win, { 8, 0 })
    227 
    228        screen:expect {
    229          grid = [[
    230                              |
    231          {1:~                   }|*4
    232          {2:[No Name]           }|
    233          www                 |
    234          xxx                 |
    235          yyy                 |
    236          ^zzz                 |
    237          {3:[No Name]           }|
    238                              |
    239        ]],
    240        }
    241 
    242        api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
    243        screen:expect {
    244          grid = [[
    245                              |
    246          {1:~                   }|*4
    247          {2:[No Name]           }|
    248          www                 |
    249          xxx                 |
    250          yyy                 |
    251          ^zzz                 |
    252          {3:[No Name] [+]       }|
    253                              |
    254        ]],
    255        }
    256 
    257        -- replacing topline keeps it the topline
    258        api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
    259        screen:expect {
    260          grid = [[
    261                              |
    262          {1:~                   }|*4
    263          {2:[No Name]           }|
    264          wwweeee             |
    265          xxx                 |
    266          yyy                 |
    267          ^zzz                 |
    268          {3:[No Name] [+]       }|
    269                              |
    270        ]],
    271        }
    272 
    273        -- inserting just before topline does not scroll up if cursor would be moved
    274        api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
    275        screen:expect {
    276          grid = [[
    277                              |
    278          {1:~                   }|*4
    279          {2:[No Name]           }|
    280          wwweeee             |
    281          xxx                 |
    282          yyy                 |
    283          ^zzz                 |
    284          {3:[No Name] [+]       }|
    285                              |
    286        ]],
    287          unchanged = true,
    288        }
    289 
    290        api.nvim_win_set_cursor(0, { 7, 0 })
    291        screen:expect {
    292          grid = [[
    293                              |
    294          {1:~                   }|*4
    295          {2:[No Name]           }|
    296          wwweeee             |
    297          xxx                 |
    298          ^yyy                 |
    299          zzz                 |
    300          {3:[No Name] [+]       }|
    301                              |
    302        ]],
    303        }
    304 
    305        api.nvim_buf_set_lines(buf, 4, 4, true, { 'mmmeeeee' })
    306        screen:expect {
    307          grid = [[
    308                              |
    309          {1:~                   }|*4
    310          {2:[No Name]           }|
    311          mmmeeeee            |
    312          wwweeee             |
    313          xxx                 |
    314          ^yyy                 |
    315          {3:[No Name] [+]       }|
    316                              |
    317        ]],
    318        }
    319      end)
    320 
    321      it('of non-current window', function()
    322        local win = api.nvim_get_current_win()
    323        local buf = api.nvim_get_current_buf()
    324 
    325        command('new')
    326        api.nvim_win_set_cursor(win, { 8, 0 })
    327 
    328        screen:expect {
    329          grid = [[
    330          ^                    |
    331          {1:~                   }|*4
    332          {3:[No Name]           }|
    333          www                 |
    334          xxx                 |
    335          yyy                 |
    336          zzz                 |
    337          {2:[No Name]           }|
    338                              |
    339        ]],
    340        }
    341 
    342        api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
    343        screen:expect {
    344          grid = [[
    345          ^                    |
    346          {1:~                   }|*4
    347          {3:[No Name]           }|
    348          www                 |
    349          xxx                 |
    350          yyy                 |
    351          zzz                 |
    352          {2:[No Name] [+]       }|
    353                              |
    354        ]],
    355        }
    356 
    357        -- replacing topline keeps it the topline
    358        api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
    359        screen:expect {
    360          grid = [[
    361          ^                    |
    362          {1:~                   }|*4
    363          {3:[No Name]           }|
    364          wwweeee             |
    365          xxx                 |
    366          yyy                 |
    367          zzz                 |
    368          {2:[No Name] [+]       }|
    369                              |
    370        ]],
    371        }
    372 
    373        api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
    374        screen:expect {
    375          grid = [[
    376          ^                    |
    377          {1:~                   }|*4
    378          {3:[No Name]           }|
    379          wwweeee             |
    380          xxx                 |
    381          yyy                 |
    382          zzz                 |
    383          {2:[No Name] [+]       }|
    384                              |
    385        ]],
    386          unchanged = true,
    387        }
    388      end)
    389 
    390      it('of split windows with same buffer', function()
    391        local win = api.nvim_get_current_win()
    392        local buf = api.nvim_get_current_buf()
    393 
    394        command('split')
    395        api.nvim_win_set_cursor(win, { 8, 0 })
    396        api.nvim_win_set_cursor(0, { 1, 0 })
    397 
    398        screen:expect {
    399          grid = [[
    400          ^aaa                 |
    401          bbb                 |
    402          ccc                 |
    403          ddd                 |
    404          www                 |
    405          {3:[No Name]           }|
    406          www                 |
    407          xxx                 |
    408          yyy                 |
    409          zzz                 |
    410          {2:[No Name]           }|
    411                              |
    412        ]],
    413        }
    414        api.nvim_buf_set_lines(buf, 0, 2, true, { 'aaabbb' })
    415 
    416        screen:expect {
    417          grid = [[
    418          ^aaabbb              |
    419          ccc                 |
    420          ddd                 |
    421          www                 |
    422          xxx                 |
    423          {3:[No Name] [+]       }|
    424          www                 |
    425          xxx                 |
    426          yyy                 |
    427          zzz                 |
    428          {2:[No Name] [+]       }|
    429                              |
    430        ]],
    431        }
    432 
    433        -- replacing topline keeps it the topline
    434        api.nvim_buf_set_lines(buf, 3, 4, true, { 'wwweeee' })
    435        screen:expect {
    436          grid = [[
    437          ^aaabbb              |
    438          ccc                 |
    439          ddd                 |
    440          wwweeee             |
    441          xxx                 |
    442          {3:[No Name] [+]       }|
    443          wwweeee             |
    444          xxx                 |
    445          yyy                 |
    446          zzz                 |
    447          {2:[No Name] [+]       }|
    448                              |
    449        ]],
    450        }
    451 
    452        api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' })
    453        screen:expect {
    454          grid = [[
    455          ^aaabbb              |
    456          ccc                 |
    457          ddd                 |
    458          mmm                 |
    459          wwweeee             |
    460          {3:[No Name] [+]       }|
    461          wwweeee             |
    462          xxx                 |
    463          yyy                 |
    464          zzz                 |
    465          {2:[No Name] [+]       }|
    466                              |
    467        ]],
    468        }
    469      end)
    470 
    471      describe('of current window when', function()
    472        before_each(function()
    473          command('new | wincmd w | setlocal modified')
    474          feed('Gk')
    475          screen:expect([[
    476                                |
    477            {1:~                   }|*4
    478            {2:[No Name]           }|
    479            www                 |
    480            xxx                 |
    481            ^yyy                 |
    482            zzz                 |
    483            {3:[No Name] [+]       }|
    484                                |
    485          ]])
    486        end)
    487 
    488        it('deleting 3 lines around topline', function()
    489          api.nvim_buf_set_lines(0, 3, 6, true, {})
    490          screen:expect([[
    491                                |
    492            {1:~                   }|*4
    493            {2:[No Name]           }|
    494            ccc                 |
    495            ^yyy                 |
    496            zzz                 |
    497            {1:~                   }|
    498            {3:[No Name] [+]       }|
    499                                |
    500          ]])
    501          eq(5, api.nvim_buf_line_count(0))
    502        end)
    503 
    504        it('deleting 3 lines around the line just before topline', function()
    505          api.nvim_buf_set_lines(0, 2, 5, true, {})
    506          screen:expect([[
    507                                |
    508            {1:~                   }|*4
    509            {2:[No Name]           }|
    510            bbb                 |
    511            xxx                 |
    512            ^yyy                 |
    513            zzz                 |
    514            {3:[No Name] [+]       }|
    515                                |
    516          ]])
    517          eq(5, api.nvim_buf_line_count(0))
    518        end)
    519 
    520        for count = 1, 4 do
    521          it(('deleting %d lines just before topline'):format(count), function()
    522            api.nvim_buf_set_lines(0, 4 - count, 4, true, {})
    523            screen:expect_unchanged()
    524            eq(8 - count, api.nvim_buf_line_count(0))
    525          end)
    526 
    527          it(('replacing %d lines just before topline with 2 lines'):format(count), function()
    528            api.nvim_buf_set_lines(0, 4 - count, 4, true, { 'eee', 'fff' })
    529            screen:expect_unchanged()
    530            eq(8 - count + 2, api.nvim_buf_line_count(0))
    531          end)
    532        end
    533 
    534        for count = 1, 3 do
    535          it(('deleting %d lines far before topline'):format(count), function()
    536            api.nvim_buf_set_lines(0, 0, count, true, {})
    537            screen:expect_unchanged()
    538            eq(8 - count, api.nvim_buf_line_count(0))
    539          end)
    540 
    541          it(('replacing %d lines far before topline with 2 lines'):format(count), function()
    542            api.nvim_buf_set_lines(0, 0, count, true, { 'eee', 'fff' })
    543            screen:expect_unchanged()
    544            eq(8 - count + 2, api.nvim_buf_line_count(0))
    545          end)
    546        end
    547      end)
    548    end)
    549 
    550    it('handles clearing out non-current buffer #24911', function()
    551      local buf = api.nvim_get_current_buf()
    552      api.nvim_buf_set_lines(buf, 0, -1, true, { 'aaa', 'bbb', 'ccc' })
    553      command('new')
    554 
    555      api.nvim_buf_set_lines(0, 0, -1, true, { 'xxx', 'yyy', 'zzz' })
    556 
    557      api.nvim_buf_set_lines(buf, 0, -1, true, {})
    558      eq({ 'xxx', 'yyy', 'zzz' }, api.nvim_buf_get_lines(0, 0, -1, true))
    559      eq({ '' }, api.nvim_buf_get_lines(buf, 0, -1, true))
    560    end)
    561  end)
    562 
    563  describe('deprecated: {get,set,del}_line', function()
    564    it('works', function()
    565      eq('', curbuf_depr('get_line', 0))
    566      curbuf_depr('set_line', 0, 'line1')
    567      eq('line1', curbuf_depr('get_line', 0))
    568      curbuf_depr('set_line', 0, 'line2')
    569      eq('line2', curbuf_depr('get_line', 0))
    570      curbuf_depr('del_line', 0)
    571      eq('', curbuf_depr('get_line', 0))
    572    end)
    573 
    574    it('get_line: out-of-bounds is an error', function()
    575      curbuf_depr('set_line', 0, 'line1.a')
    576      eq(1, curbuf_depr('line_count')) -- sanity
    577      eq(false, pcall(curbuf_depr, 'get_line', 1))
    578      eq(false, pcall(curbuf_depr, 'get_line', -2))
    579    end)
    580 
    581    it('set_line, del_line: out-of-bounds is an error', function()
    582      curbuf_depr('set_line', 0, 'line1.a')
    583      eq(false, pcall(curbuf_depr, 'set_line', 1, 'line1.b'))
    584      eq(false, pcall(curbuf_depr, 'set_line', -2, 'line1.b'))
    585      eq(false, pcall(curbuf_depr, 'del_line', 2))
    586      eq(false, pcall(curbuf_depr, 'del_line', -3))
    587    end)
    588 
    589    it('can handle NULs', function()
    590      curbuf_depr('set_line', 0, 'ab\0cd')
    591      eq('ab\0cd', curbuf_depr('get_line', 0))
    592    end)
    593  end)
    594 
    595  describe('deprecated: {get,set}_line_slice', function()
    596    it('get_line_slice: out-of-bounds returns empty array', function()
    597      curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' })
    598      eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity
    599 
    600      eq({}, curbuf_depr('get_line_slice', 2, 3, false, true))
    601      eq({}, curbuf_depr('get_line_slice', 3, 9, true, true))
    602      eq({}, curbuf_depr('get_line_slice', 3, -1, true, true))
    603      eq({}, curbuf_depr('get_line_slice', -3, -4, false, true))
    604      eq({}, curbuf_depr('get_line_slice', -4, -5, true, true))
    605    end)
    606 
    607    it('set_line_slice: out-of-bounds extends past end', function()
    608      curbuf_depr('set_line_slice', 0, 0, true, true, { 'a', 'b', 'c' })
    609      eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 2, true, true)) --sanity
    610 
    611      eq({ 'c' }, curbuf_depr('get_line_slice', -1, 4, true, true))
    612      eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, 5, true, true))
    613      curbuf_depr('set_line_slice', 4, 5, true, true, { 'd' })
    614      eq({ 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true))
    615      curbuf_depr('set_line_slice', -4, -5, true, true, { 'e' })
    616      eq({ 'e', 'a', 'b', 'c', 'd' }, curbuf_depr('get_line_slice', 0, 5, true, true))
    617    end)
    618 
    619    it('works', function()
    620      eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    621      -- Replace buffer
    622      curbuf_depr('set_line_slice', 0, -1, true, true, { 'a', 'b', 'c' })
    623      eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    624      eq({ 'b', 'c' }, curbuf_depr('get_line_slice', 1, -1, true, true))
    625      eq({ 'b' }, curbuf_depr('get_line_slice', 1, 2, true, false))
    626      eq({}, curbuf_depr('get_line_slice', 1, 1, true, false))
    627      eq({ 'a', 'b' }, curbuf_depr('get_line_slice', 0, -1, true, false))
    628      eq({ 'b' }, curbuf_depr('get_line_slice', 1, -1, true, false))
    629      eq({ 'b', 'c' }, curbuf_depr('get_line_slice', -2, -1, true, true))
    630      curbuf_depr('set_line_slice', 1, 2, true, false, { 'a', 'b', 'c' })
    631      eq({ 'a', 'a', 'b', 'c', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    632      curbuf_depr('set_line_slice', -1, -1, true, true, { 'a', 'b', 'c' })
    633      eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    634      curbuf_depr('set_line_slice', 0, -3, true, false, {})
    635      eq({ 'a', 'b', 'c' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    636      curbuf_depr('set_line_slice', 0, -1, true, true, {})
    637      eq({ '' }, curbuf_depr('get_line_slice', 0, -1, true, true))
    638    end)
    639  end)
    640 
    641  describe_lua_and_rpc('nvim_buf_get_lines, nvim_buf_set_lines', function(lua_or_rpc)
    642    local function get_lines(...)
    643      return lua_or_rpc.nvim_buf_get_lines(0, ...)
    644    end
    645 
    646    local function set_lines(...)
    647      return lua_or_rpc.nvim_buf_set_lines(0, ...)
    648    end
    649 
    650    local function line_count()
    651      return lua_or_rpc.nvim_buf_line_count(0)
    652    end
    653 
    654    it('fails correctly when input is not valid', function()
    655      eq(1, lua_or_rpc.nvim_buf_get_number(0))
    656      eq(
    657        [['replacement string' item contains newlines]],
    658        pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'b\na' })
    659      )
    660    end)
    661 
    662    it("fails if 'nomodifiable'", function()
    663      command('set nomodifiable')
    664      eq(
    665        [[Buffer is not 'modifiable']],
    666        pcall_err(lua_or_rpc.nvim_buf_set_lines, 1, 1, 2, false, { 'a', 'b' })
    667      )
    668    end)
    669 
    670    it('has correct line_count when inserting and deleting', function()
    671      eq(1, line_count())
    672      set_lines(-1, -1, true, { 'line' })
    673      eq(2, line_count())
    674      set_lines(-1, -1, true, { 'line' })
    675      eq(3, line_count())
    676      set_lines(-2, -1, true, {})
    677      eq(2, line_count())
    678      set_lines(-2, -1, true, {})
    679      set_lines(-2, -1, true, {})
    680      -- There's always at least one line
    681      eq(1, line_count())
    682    end)
    683 
    684    it('can get, set and delete a single line', function()
    685      eq({ '' }, get_lines(0, 1, true))
    686      set_lines(0, 1, true, { 'line1' })
    687      eq({ 'line1' }, get_lines(0, 1, true))
    688      set_lines(0, 1, true, { 'line2' })
    689      eq({ 'line2' }, get_lines(0, 1, true))
    690      set_lines(0, 1, true, {})
    691      eq({ '' }, get_lines(0, 1, true))
    692    end)
    693 
    694    it('can get a single line with strict indexing', function()
    695      set_lines(0, 1, true, { 'line1.a' })
    696      eq(1, line_count()) -- sanity
    697      eq('Index out of bounds', pcall_err(get_lines, 1, 2, true))
    698      eq('Index out of bounds', pcall_err(get_lines, -3, -2, true))
    699    end)
    700 
    701    it('can get a single line with non-strict indexing', function()
    702      set_lines(0, 1, true, { 'line1.a' })
    703      eq(1, line_count()) -- sanity
    704      eq({}, get_lines(1, 2, false))
    705      eq({}, get_lines(-3, -2, false))
    706    end)
    707 
    708    it('can set and delete a single line with strict indexing', function()
    709      set_lines(0, 1, true, { 'line1.a' })
    710      eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, { 'line1.b' }))
    711      eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, { 'line1.c' }))
    712      eq({ 'line1.a' }, get_lines(0, -1, true))
    713      eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {}))
    714      eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {}))
    715      eq({ 'line1.a' }, get_lines(0, -1, true))
    716    end)
    717 
    718    it('can set and delete a single line with non-strict indexing', function()
    719      set_lines(0, 1, true, { 'line1.a' })
    720      set_lines(1, 2, false, { 'line1.b' })
    721      set_lines(-4, -3, false, { 'line1.c' })
    722      eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true))
    723      set_lines(3, 4, false, {})
    724      set_lines(-5, -4, false, {})
    725      eq({ 'line1.c', 'line1.a', 'line1.b' }, get_lines(0, -1, true))
    726    end)
    727 
    728    it('can handle NULs', function()
    729      set_lines(0, 1, true, { 'ab\0cd' })
    730      eq({ 'ab\0cd' }, get_lines(0, -1, true))
    731    end)
    732 
    733    it('works with multiple lines', function()
    734      eq({ '' }, get_lines(0, -1, true))
    735      -- Replace buffer
    736      for _, mode in pairs({ false, true }) do
    737        set_lines(0, -1, mode, { 'a', 'b', 'c' })
    738        eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode))
    739        eq({ 'b', 'c' }, get_lines(1, -1, mode))
    740        eq({ 'b' }, get_lines(1, 2, mode))
    741        eq({}, get_lines(1, 1, mode))
    742        eq({ 'a', 'b' }, get_lines(0, -2, mode))
    743        eq({ 'b' }, get_lines(1, -2, mode))
    744        eq({ 'b', 'c' }, get_lines(-3, -1, mode))
    745        set_lines(1, 2, mode, { 'a', 'b', 'c' })
    746        eq({ 'a', 'a', 'b', 'c', 'c' }, get_lines(0, -1, mode))
    747        set_lines(-2, -1, mode, { 'a', 'b', 'c' })
    748        eq({ 'a', 'a', 'b', 'c', 'a', 'b', 'c' }, get_lines(0, -1, mode))
    749        set_lines(0, -4, mode, {})
    750        eq({ 'a', 'b', 'c' }, get_lines(0, -1, mode))
    751        set_lines(0, -1, mode, {})
    752        eq({ '' }, get_lines(0, -1, mode))
    753      end
    754    end)
    755 
    756    it('can get line ranges with non-strict indexing', function()
    757      set_lines(0, -1, true, { 'a', 'b', 'c' })
    758      eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
    759 
    760      eq({}, get_lines(3, 4, false))
    761      eq({}, get_lines(3, 10, false))
    762      eq({}, get_lines(-5, -5, false))
    763      eq({}, get_lines(3, -1, false))
    764      eq({}, get_lines(-3, -4, false))
    765    end)
    766 
    767    it('can get line ranges with strict indexing', function()
    768      set_lines(0, -1, true, { 'a', 'b', 'c' })
    769      eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
    770 
    771      eq('Index out of bounds', pcall_err(get_lines, 3, 4, true))
    772      eq('Index out of bounds', pcall_err(get_lines, 3, 10, true))
    773      eq('Index out of bounds', pcall_err(get_lines, -5, -5, true))
    774      -- empty or inverted ranges are not errors
    775      eq({}, get_lines(3, -1, true))
    776      eq({}, get_lines(-3, -4, true))
    777    end)
    778 
    779    it('set_lines: out-of-bounds can extend past end', function()
    780      set_lines(0, -1, true, { 'a', 'b', 'c' })
    781      eq({ 'a', 'b', 'c' }, get_lines(0, -1, true)) --sanity
    782 
    783      eq({ 'c' }, get_lines(-2, 5, false))
    784      eq({ 'a', 'b', 'c' }, get_lines(0, 6, false))
    785      eq('Index out of bounds', pcall_err(set_lines, 4, 6, true, { 'd' }))
    786      set_lines(4, 6, false, { 'd' })
    787      eq({ 'a', 'b', 'c', 'd' }, get_lines(0, -1, true))
    788      eq('Index out of bounds', pcall_err(set_lines, -6, -6, true, { 'e' }))
    789      set_lines(-6, -6, false, { 'e' })
    790      eq({ 'e', 'a', 'b', 'c', 'd' }, get_lines(0, -1, true))
    791    end)
    792 
    793    it('set_lines on alternate buffer does not access invalid line (E315)', function()
    794      command('set hidden')
    795      insert('Initial file')
    796      command('enew')
    797      insert([[
    798      More
    799      Lines
    800      Than
    801      In
    802      The
    803      Other
    804      Buffer]])
    805      command('$')
    806      eq(true, pcall(api.nvim_buf_set_lines, 0, 0, 1, false, { 'test' }))
    807    end)
    808 
    809    it("set_lines of invisible buffer doesn't move cursor in current window", function()
    810      local screen = Screen.new(20, 5)
    811 
    812      insert([[
    813        Who would win?
    814        A real window
    815        with proper text]])
    816      local buf = lua_or_rpc.nvim_create_buf(false, true)
    817      screen:expect([[
    818        Who would win?      |
    819        A real window       |
    820        with proper tex^t    |
    821        {1:~                   }|
    822                            |
    823      ]])
    824 
    825      lua_or_rpc.nvim_buf_set_lines(buf, 0, -1, true, { 'or some', 'scratchy text' })
    826      feed('i') -- provoke redraw
    827      screen:expect([[
    828        Who would win?      |
    829        A real window       |
    830        with proper tex^t    |
    831        {1:~                   }|
    832        {5:-- INSERT --}        |
    833      ]])
    834    end)
    835 
    836    it('set_lines on hidden buffer preserves "previous window" #9741', function()
    837      insert([[
    838        visible buffer line 1
    839        line 2
    840      ]])
    841      local hiddenbuf = lua_or_rpc.nvim_create_buf(false, true)
    842      command('vsplit')
    843      command('vsplit')
    844      feed('<c-w>l<c-w>l<c-w>l')
    845      eq(3, fn.winnr())
    846      feed('<c-w>h')
    847      eq(2, fn.winnr())
    848      lua_or_rpc.nvim_buf_set_lines(hiddenbuf, 0, -1, true, { 'hidden buffer line 1', 'line 2' })
    849      feed('<c-w>p')
    850      eq(3, fn.winnr())
    851    end)
    852 
    853    it('set_lines on unloaded buffer #8659 #22670', function()
    854      local bufnr = api.nvim_get_current_buf()
    855      lua_or_rpc.nvim_buf_set_lines(bufnr, 0, -1, false, { 'a', 'b', 'c' })
    856      lua_or_rpc.nvim_buf_set_name(bufnr, 'set_lines')
    857      finally(function()
    858        os.remove('set_lines')
    859      end)
    860      command('write!')
    861      command('new')
    862      command('bunload! ' .. bufnr)
    863      local new_bufnr = fn.bufnr('set_lines', true)
    864      lua_or_rpc.nvim_buf_set_lines(new_bufnr, 0, -1, false, {})
    865      eq({ '' }, lua_or_rpc.nvim_buf_get_lines(new_bufnr, 0, -1, false))
    866      eq(true, api.nvim_buf_is_loaded(new_bufnr))
    867    end)
    868  end)
    869 
    870  describe('nvim_buf_set_text', function()
    871    local function get_lines(...)
    872      return api.nvim_buf_get_lines(0, ...)
    873    end
    874 
    875    local function set_text(...)
    876      return api.nvim_buf_set_text(0, ...)
    877    end
    878 
    879    it('works', function()
    880      insert([[
    881      hello foo!
    882      text
    883      ]])
    884 
    885      eq({ 'hello foo!' }, get_lines(0, 1, true))
    886 
    887      -- can replace a single word
    888      set_text(0, 6, 0, 9, { 'world' })
    889      eq({ 'hello world!', 'text' }, get_lines(0, 2, true))
    890 
    891      -- can insert text
    892      set_text(0, 0, 0, 0, { 'well ' })
    893      eq({ 'well hello world!', 'text' }, get_lines(0, 2, true))
    894 
    895      -- can delete text
    896      set_text(0, 0, 0, 5, { '' })
    897      eq({ 'hello world!', 'text' }, get_lines(0, 2, true))
    898 
    899      -- can replace with multiple lines
    900      set_text(0, 6, 0, 11, { 'foo', 'wo', 'more' })
    901      eq({ 'hello foo', 'wo', 'more!', 'text' }, get_lines(0, 4, true))
    902 
    903      -- will join multiple lines if needed
    904      set_text(0, 6, 3, 4, { 'bar' })
    905      eq({ 'hello bar' }, get_lines(0, 1, true))
    906 
    907      -- can use negative line numbers
    908      set_text(-2, 0, -2, 5, { 'goodbye' })
    909      eq({ 'goodbye bar', '' }, get_lines(0, -1, true))
    910 
    911      set_text(-1, 0, -1, 0, { 'text' })
    912      eq({ 'goodbye bar', 'text' }, get_lines(0, 2, true))
    913 
    914      -- can append to a line
    915      set_text(1, 4, -1, 4, { ' and', 'more' })
    916      eq({ 'goodbye bar', 'text and', 'more' }, get_lines(0, 3, true))
    917 
    918      -- can use negative column numbers
    919      set_text(0, -5, 0, -1, { '!' })
    920      eq({ 'goodbye!' }, get_lines(0, 1, true))
    921    end)
    922 
    923    it('works with undo', function()
    924      insert([[
    925        hello world!
    926        foo bar
    927        ]])
    928 
    929      -- setting text
    930      set_text(0, 0, 0, 0, { 'well ' })
    931      feed('u')
    932      eq({ 'hello world!' }, get_lines(0, 1, true))
    933 
    934      -- deleting text
    935      set_text(0, 0, 0, 6, { '' })
    936      feed('u')
    937      eq({ 'hello world!' }, get_lines(0, 1, true))
    938 
    939      -- inserting newlines
    940      set_text(0, 0, 0, 0, { 'hello', 'mr ' })
    941      feed('u')
    942      eq({ 'hello world!' }, get_lines(0, 1, true))
    943 
    944      -- deleting newlines
    945      set_text(0, 0, 1, 4, { 'hello' })
    946      feed('u')
    947      eq({ 'hello world!' }, get_lines(0, 1, true))
    948    end)
    949 
    950    it('updates the cursor position', function()
    951      insert([[
    952      hello world!
    953      ]])
    954 
    955      -- position the cursor on `!`
    956      api.nvim_win_set_cursor(0, { 1, 11 })
    957      -- replace 'world' with 'foo'
    958      set_text(0, 6, 0, 11, { 'foo' })
    959      eq('hello foo!', curbuf_depr('get_line', 0))
    960      -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
    961      eq({ 1, 9 }, api.nvim_win_get_cursor(0))
    962    end)
    963 
    964    it('updates the cursor position in non-current window', function()
    965      insert([[
    966      hello world!]])
    967 
    968      -- position the cursor on `!`
    969      api.nvim_win_set_cursor(0, { 1, 11 })
    970 
    971      local win = api.nvim_get_current_win()
    972      local buf = api.nvim_get_current_buf()
    973 
    974      command('new')
    975 
    976      -- replace 'world' with 'foo'
    977      api.nvim_buf_set_text(buf, 0, 6, 0, 11, { 'foo' })
    978      eq({ 'hello foo!' }, api.nvim_buf_get_lines(buf, 0, -1, true))
    979      -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
    980      eq({ 1, 9 }, api.nvim_win_get_cursor(win))
    981    end)
    982 
    983    it('updates the cursor position in TWO non-current windows', function()
    984      insert([[
    985      hello world!]])
    986 
    987      -- position the cursor on `!`
    988      api.nvim_win_set_cursor(0, { 1, 11 })
    989      local win = api.nvim_get_current_win()
    990      local buf = api.nvim_get_current_buf()
    991 
    992      command('split')
    993      local win2 = api.nvim_get_current_win()
    994      -- position the cursor on `w`
    995      api.nvim_win_set_cursor(0, { 1, 6 })
    996 
    997      command('new')
    998 
    999      -- replace 'hello' with 'foo'
   1000      api.nvim_buf_set_text(buf, 0, 0, 0, 5, { 'foo' })
   1001      eq({ 'foo world!' }, api.nvim_buf_get_lines(buf, 0, -1, true))
   1002 
   1003      -- both cursors should be moved left by two columns (replacement is shorter by 2 chars)
   1004      eq({ 1, 9 }, api.nvim_win_get_cursor(win))
   1005      eq({ 1, 4 }, api.nvim_win_get_cursor(win2))
   1006    end)
   1007 
   1008    describe('when text is being added right at cursor position #22526', function()
   1009      it('updates the cursor position in NORMAL mode', function()
   1010        insert([[
   1011        abcd]])
   1012 
   1013        -- position the cursor on 'c'
   1014        api.nvim_win_set_cursor(0, { 1, 2 })
   1015        -- add 'xxx' before 'c'
   1016        set_text(0, 2, 0, 2, { 'xxx' })
   1017        eq({ 'abxxxcd' }, get_lines(0, -1, true))
   1018        -- cursor should be on 'c'
   1019        eq({ 1, 5 }, api.nvim_win_get_cursor(0))
   1020      end)
   1021 
   1022      it('updates the cursor position only in non-current window when in INSERT mode', function()
   1023        insert([[
   1024        abcd]])
   1025 
   1026        -- position the cursor on 'c'
   1027        api.nvim_win_set_cursor(0, { 1, 2 })
   1028        -- open vertical split
   1029        feed('<c-w>v')
   1030        -- get into INSERT mode to treat cursor
   1031        -- as being after 'b', not on 'c'
   1032        feed('i')
   1033        -- add 'xxx' between 'b' and 'c'
   1034        set_text(0, 2, 0, 2, { 'xxx' })
   1035        eq({ 'abxxxcd' }, get_lines(0, -1, true))
   1036        -- in the current window cursor should stay after 'b'
   1037        eq({ 1, 2 }, api.nvim_win_get_cursor(0))
   1038        -- quit INSERT mode
   1039        feed('<esc>')
   1040        -- close current window
   1041        feed('<c-w>c')
   1042        -- in another window cursor should be on 'c'
   1043        eq({ 1, 5 }, api.nvim_win_get_cursor(0))
   1044      end)
   1045    end)
   1046 
   1047    describe('when text is being deleted right at cursor position', function()
   1048      it('leaves cursor at the same position in NORMAL mode', function()
   1049        insert([[
   1050        abcd]])
   1051 
   1052        -- position the cursor on 'b'
   1053        api.nvim_win_set_cursor(0, { 1, 1 })
   1054        -- delete 'b'
   1055        set_text(0, 1, 0, 2, {})
   1056        eq({ 'acd' }, get_lines(0, -1, true))
   1057        -- cursor is now on 'c'
   1058        eq({ 1, 1 }, api.nvim_win_get_cursor(0))
   1059      end)
   1060 
   1061      it('maintains INSERT-mode cursor position current/non-current window', function()
   1062        insert([[
   1063        abcd]])
   1064 
   1065        -- position the cursor on 'b'
   1066        api.nvim_win_set_cursor(0, { 1, 1 })
   1067        -- open vertical split
   1068        feed('<c-w>v')
   1069        -- get into INSERT mode to treat cursor
   1070        -- as being after 'a', not on 'b'
   1071        feed('i')
   1072        -- delete 'b'
   1073        set_text(0, 1, 0, 2, {})
   1074        eq({ 'acd' }, get_lines(0, -1, true))
   1075        -- cursor in the current window should stay after 'a'
   1076        eq({ 1, 1 }, api.nvim_win_get_cursor(0))
   1077        -- quit INSERT mode
   1078        feed('<esc>')
   1079        -- close current window
   1080        feed('<c-w>c')
   1081        -- cursor in non-current window should stay on 'c'
   1082        eq({ 1, 1 }, api.nvim_win_get_cursor(0))
   1083      end)
   1084    end)
   1085 
   1086    describe('when cursor is inside replaced row range', function()
   1087      it('maintains cursor position if at start_row, but before start_col', function()
   1088        insert([[
   1089        This should be first
   1090        then there is a line we do not want
   1091        and finally the last one]])
   1092 
   1093        -- position the cursor on ' ' before 'first'
   1094        api.nvim_win_set_cursor(0, { 1, 14 })
   1095 
   1096        set_text(0, 15, 2, 11, {
   1097          'the line we do not want',
   1098          'but hopefully',
   1099        })
   1100 
   1101        eq({
   1102          'This should be the line we do not want',
   1103          'but hopefully the last one',
   1104        }, get_lines(0, -1, true))
   1105        -- cursor should stay at the same position
   1106        eq({ 1, 14 }, api.nvim_win_get_cursor(0))
   1107      end)
   1108 
   1109      it('maintains cursor position if at start_row and column is still valid', function()
   1110        insert([[
   1111        This should be first
   1112        then there is a line we do not want
   1113        and finally the last one]])
   1114 
   1115        -- position the cursor on 'f' in 'first'
   1116        api.nvim_win_set_cursor(0, { 1, 15 })
   1117 
   1118        set_text(0, 15, 2, 11, {
   1119          'the line we do not want',
   1120          'but hopefully',
   1121        })
   1122 
   1123        eq({
   1124          'This should be the line we do not want',
   1125          'but hopefully the last one',
   1126        }, get_lines(0, -1, true))
   1127        -- cursor should stay at the same position
   1128        eq({ 1, 15 }, api.nvim_win_get_cursor(0))
   1129      end)
   1130 
   1131      it('adjusts cursor column to keep it valid if start_row got smaller', function()
   1132        insert([[
   1133        This should be first
   1134        then there is a line we do not want
   1135        and finally the last one]])
   1136 
   1137        -- position the cursor on 't' in 'first'
   1138        api.nvim_win_set_cursor(0, { 1, 19 })
   1139 
   1140        local cursor = exec_lua([[
   1141          vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
   1142          return vim.api.nvim_win_get_cursor(0)
   1143        ]])
   1144 
   1145        eq({ 'This should be last' }, get_lines(0, -1, true))
   1146        -- cursor should end up on 't' in 'last'
   1147        eq({ 1, 18 }, api.nvim_win_get_cursor(0))
   1148        -- immediate call to nvim_win_get_cursor should have returned the same position
   1149        eq({ 1, 18 }, cursor)
   1150      end)
   1151 
   1152      it('adjusts cursor column to keep it valid if start_row decreased in INSERT mode', function()
   1153        insert([[
   1154        This should be first
   1155        then there is a line we do not want
   1156        and finally the last one]])
   1157 
   1158        -- position the cursor on 't' in 'first'
   1159        api.nvim_win_set_cursor(0, { 1, 19 })
   1160        -- enter INSERT mode to treat cursor as being after 't'
   1161        feed('a')
   1162 
   1163        local cursor = exec_lua([[
   1164          vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
   1165          return vim.api.nvim_win_get_cursor(0)
   1166        ]])
   1167 
   1168        eq({ 'This should be last' }, get_lines(0, -1, true))
   1169        -- cursor should end up after 't' in 'last'
   1170        eq({ 1, 19 }, api.nvim_win_get_cursor(0))
   1171        -- immediate call to nvim_win_get_cursor should have returned the same position
   1172        eq({ 1, 19 }, cursor)
   1173      end)
   1174 
   1175      it('adjusts cursor to valid column in row after start_row if it got smaller', function()
   1176        insert([[
   1177        This should be first
   1178        then there is a line we do not want
   1179        and finally the last one]])
   1180 
   1181        -- position the cursor on 'w' in 'want'
   1182        api.nvim_win_set_cursor(0, { 2, 31 })
   1183 
   1184        local cursor = exec_lua([[
   1185          vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1186            '1',
   1187            'then 2',
   1188            'and then',
   1189          })
   1190          return vim.api.nvim_win_get_cursor(0)
   1191        ]])
   1192 
   1193        eq({
   1194          'This should be 1',
   1195          'then 2',
   1196          'and then the last one',
   1197        }, get_lines(0, -1, true))
   1198        -- cursor column should end up at the end of a row
   1199        eq({ 2, 5 }, api.nvim_win_get_cursor(0))
   1200        -- immediate call to nvim_win_get_cursor should have returned the same position
   1201        eq({ 2, 5 }, cursor)
   1202      end)
   1203 
   1204      it(
   1205        'adjusts cursor to valid column in row after start_row if it got smaller in INSERT mode',
   1206        function()
   1207          insert([[
   1208        This should be first
   1209        then there is a line we do not want
   1210        and finally the last one]])
   1211 
   1212          -- position the cursor on 'w' in 'want'
   1213          api.nvim_win_set_cursor(0, { 2, 31 })
   1214          -- enter INSERT mode
   1215          feed('a')
   1216 
   1217          local cursor = exec_lua([[
   1218          vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1219            '1',
   1220            'then 2',
   1221            'and then',
   1222          })
   1223          return vim.api.nvim_win_get_cursor(0)
   1224        ]])
   1225 
   1226          eq({
   1227            'This should be 1',
   1228            'then 2',
   1229            'and then the last one',
   1230          }, get_lines(0, -1, true))
   1231          -- cursor column should end up at the end of a row
   1232          eq({ 2, 6 }, api.nvim_win_get_cursor(0))
   1233          -- immediate call to nvim_win_get_cursor should have returned the same position
   1234          eq({ 2, 6 }, cursor)
   1235        end
   1236      )
   1237 
   1238      it('adjusts cursor line and column to keep it inside replacement range', function()
   1239        insert([[
   1240        This should be first
   1241        then there is a line we do not want
   1242        and finally the last one]])
   1243 
   1244        -- position the cursor on 'n' in 'finally'
   1245        api.nvim_win_set_cursor(0, { 3, 6 })
   1246 
   1247        local cursor = exec_lua([[
   1248          vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1249            'the line we do not want',
   1250            'but hopefully',
   1251          })
   1252          return vim.api.nvim_win_get_cursor(0)
   1253        ]])
   1254 
   1255        eq({
   1256          'This should be the line we do not want',
   1257          'but hopefully the last one',
   1258        }, get_lines(0, -1, true))
   1259        -- cursor should end up on 'y' in 'hopefully'
   1260        -- to stay in the range, because it got smaller
   1261        eq({ 2, 12 }, api.nvim_win_get_cursor(0))
   1262        -- immediate call to nvim_win_get_cursor should have returned the same position
   1263        eq({ 2, 12 }, cursor)
   1264      end)
   1265 
   1266      it('adjusts cursor line and column if replacement is empty', function()
   1267        insert([[
   1268        This should be first
   1269        then there is a line we do not want
   1270        and finally the last one]])
   1271 
   1272        -- position the cursor on 'r' in 'there'
   1273        api.nvim_win_set_cursor(0, { 2, 8 })
   1274 
   1275        local cursor = exec_lua([[
   1276          vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {})
   1277          return vim.api.nvim_win_get_cursor(0)
   1278        ]])
   1279 
   1280        eq({ 'This should be the last one' }, get_lines(0, -1, true))
   1281        -- cursor should end up on the next column after deleted range
   1282        eq({ 1, 15 }, api.nvim_win_get_cursor(0))
   1283        -- immediate call to nvim_win_get_cursor should have returned the same position
   1284        eq({ 1, 15 }, cursor)
   1285      end)
   1286 
   1287      it('adjusts cursor line and column if replacement is empty and start_col == 0', function()
   1288        insert([[
   1289        This should be first
   1290        then there is a line we do not want
   1291        and finally the last one]])
   1292 
   1293        -- position the cursor on 'r' in 'there'
   1294        api.nvim_win_set_cursor(0, { 2, 8 })
   1295 
   1296        local cursor = exec_lua([[
   1297          vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {})
   1298          return vim.api.nvim_win_get_cursor(0)
   1299        ]])
   1300 
   1301        eq({ 'finally the last one' }, get_lines(0, -1, true))
   1302        -- cursor should end up in column 0
   1303        eq({ 1, 0 }, api.nvim_win_get_cursor(0))
   1304        -- immediate call to nvim_win_get_cursor should have returned the same position
   1305        eq({ 1, 0 }, cursor)
   1306      end)
   1307 
   1308      it('adjusts cursor column if replacement ends at cursor row, after cursor column', function()
   1309        insert([[
   1310        This should be first
   1311        then there is a line we do not want
   1312        and finally the last one]])
   1313 
   1314        -- position the cursor on 'y' in 'finally'
   1315        api.nvim_win_set_cursor(0, { 3, 10 })
   1316        set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
   1317 
   1318        eq({
   1319          'This should be 1',
   1320          'this 2',
   1321          'and then the last one',
   1322        }, get_lines(0, -1, true))
   1323        -- cursor should end up on 'n' in 'then'
   1324        eq({ 3, 7 }, api.nvim_win_get_cursor(0))
   1325      end)
   1326 
   1327      it(
   1328        'adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode',
   1329        function()
   1330          insert([[
   1331        This should be first
   1332        then there is a line we do not want
   1333        and finally the last one]])
   1334 
   1335          -- position the cursor on 'y' at 'finally'
   1336          api.nvim_win_set_cursor(0, { 3, 10 })
   1337          -- enter INSERT mode to treat cursor as being between 'l' and 'y'
   1338          feed('i')
   1339          set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
   1340 
   1341          eq({
   1342            'This should be 1',
   1343            'this 2',
   1344            'and then the last one',
   1345          }, get_lines(0, -1, true))
   1346          -- cursor should end up after 'n' in 'then'
   1347          eq({ 3, 8 }, api.nvim_win_get_cursor(0))
   1348        end
   1349      )
   1350 
   1351      it('adjusts cursor column if replacement is inside of a single line', function()
   1352        insert([[
   1353        This should be first
   1354        then there is a line we do not want
   1355        and finally the last one]])
   1356 
   1357        -- position the cursor on 'y' in 'finally'
   1358        api.nvim_win_set_cursor(0, { 3, 10 })
   1359        set_text(2, 4, 2, 11, { 'then' })
   1360 
   1361        eq({
   1362          'This should be first',
   1363          'then there is a line we do not want',
   1364          'and then the last one',
   1365        }, get_lines(0, -1, true))
   1366        -- cursor should end up on 'n' in 'then'
   1367        eq({ 3, 7 }, api.nvim_win_get_cursor(0))
   1368      end)
   1369 
   1370      it('does not move cursor column after end of a line', function()
   1371        insert([[
   1372        This should be the only line here
   1373        !!!]])
   1374 
   1375        -- position cursor on the last '1'
   1376        api.nvim_win_set_cursor(0, { 2, 2 })
   1377 
   1378        local cursor = exec_lua([[
   1379          vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {})
   1380          return vim.api.nvim_win_get_cursor(0)
   1381        ]])
   1382 
   1383        eq({ 'This should be the only line here' }, get_lines(0, -1, true))
   1384        -- cursor should end up on '!'
   1385        eq({ 1, 32 }, api.nvim_win_get_cursor(0))
   1386        -- immediate call to nvim_win_get_cursor should have returned the same position
   1387        eq({ 1, 32 }, cursor)
   1388      end)
   1389 
   1390      it('does not move cursor column before start of a line', function()
   1391        insert('\n!!!')
   1392 
   1393        -- position cursor on the last '1'
   1394        api.nvim_win_set_cursor(0, { 2, 2 })
   1395 
   1396        local cursor = exec_lua([[
   1397          vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {})
   1398          return vim.api.nvim_win_get_cursor(0)
   1399        ]])
   1400 
   1401        eq({ '' }, get_lines(0, -1, true))
   1402        -- cursor should end up on '!'
   1403        eq({ 1, 0 }, api.nvim_win_get_cursor(0))
   1404        -- immediate call to nvim_win_get_cursor should have returned the same position
   1405        eq({ 1, 0 }, cursor)
   1406      end)
   1407 
   1408      describe('with virtualedit', function()
   1409        it('adjusts cursor line/col to keep inside replacement range if not after eol', function()
   1410          insert([[
   1411          This should be first
   1412          then there is a line we do not want
   1413          and finally the last one]])
   1414 
   1415          -- position cursor on 't' in 'want'
   1416          api.nvim_win_set_cursor(0, { 2, 34 })
   1417          -- turn on virtualedit
   1418          command('set virtualedit=all')
   1419 
   1420          local cursor = exec_lua([[
   1421            vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1422              'the line we do not want',
   1423              'but hopefully',
   1424            })
   1425            return vim.api.nvim_win_get_cursor(0)
   1426          ]])
   1427 
   1428          eq({
   1429            'This should be the line we do not want',
   1430            'but hopefully the last one',
   1431          }, get_lines(0, -1, true))
   1432          -- cursor should end up on 'y' in 'hopefully'
   1433          -- to stay in the range
   1434          eq({ 2, 12 }, api.nvim_win_get_cursor(0))
   1435          -- immediate call to nvim_win_get_cursor should have returned the same position
   1436          eq({ 2, 12 }, cursor)
   1437          -- coladd should be 0
   1438          eq(0, fn.winsaveview().coladd)
   1439        end)
   1440 
   1441        it('does not change cursor screen column when cursor >EOL and row got shorter', function()
   1442          insert([[
   1443          This should be first
   1444          then there is a line we do not want
   1445          and finally the last one]])
   1446 
   1447          -- position cursor on 't' in 'want'
   1448          api.nvim_win_set_cursor(0, { 2, 34 })
   1449          -- turn on virtualedit
   1450          command('set virtualedit=all')
   1451          -- move cursor after eol
   1452          fn.winrestview({ coladd = 5 })
   1453 
   1454          local cursor = exec_lua([[
   1455            vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1456              'the line we do not want',
   1457              'but hopefully',
   1458            })
   1459            return vim.api.nvim_win_get_cursor(0)
   1460          ]])
   1461 
   1462          eq({
   1463            'This should be the line we do not want',
   1464            'but hopefully the last one',
   1465          }, get_lines(0, -1, true))
   1466          -- cursor should end up at eol of a new row
   1467          eq({ 2, 26 }, api.nvim_win_get_cursor(0))
   1468          -- immediate call to nvim_win_get_cursor should have returned the same position
   1469          eq({ 2, 26 }, cursor)
   1470          -- coladd should be increased so that cursor stays in the same screen column
   1471          eq(13, fn.winsaveview().coladd)
   1472        end)
   1473 
   1474        it(
   1475          'does not change cursor screen column when cursor is after eol and row got longer',
   1476          function()
   1477            insert([[
   1478          This should be first
   1479          then there is a line we do not want
   1480          and finally the last one]])
   1481 
   1482            -- position cursor on 't' in 'first'
   1483            api.nvim_win_set_cursor(0, { 1, 19 })
   1484            -- turn on virtualedit
   1485            command('set virtualedit=all')
   1486            -- move cursor after eol
   1487            fn.winrestview({ coladd = 21 })
   1488 
   1489            local cursor = exec_lua([[
   1490            vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1491              'the line we do not want',
   1492              'but hopefully',
   1493            })
   1494            return vim.api.nvim_win_get_cursor(0)
   1495          ]])
   1496 
   1497            eq({
   1498              'This should be the line we do not want',
   1499              'but hopefully the last one',
   1500            }, get_lines(0, -1, true))
   1501            -- cursor should end up at eol of a new row
   1502            eq({ 1, 38 }, api.nvim_win_get_cursor(0))
   1503            -- immediate call to nvim_win_get_cursor should have returned the same position
   1504            eq({ 1, 38 }, cursor)
   1505            -- coladd should be increased so that cursor stays in the same screen column
   1506            eq(2, fn.winsaveview().coladd)
   1507          end
   1508        )
   1509 
   1510        it(
   1511          'does not change cursor screen column when cursor is after eol and row extended past cursor column',
   1512          function()
   1513            insert([[
   1514          This should be first
   1515          then there is a line we do not want
   1516          and finally the last one]])
   1517 
   1518            -- position cursor on 't' in 'first'
   1519            api.nvim_win_set_cursor(0, { 1, 19 })
   1520            -- turn on virtualedit
   1521            command('set virtualedit=all')
   1522            -- move cursor after eol just a bit
   1523            fn.winrestview({ coladd = 3 })
   1524 
   1525            local cursor = exec_lua([[
   1526            vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
   1527              'the line we do not want',
   1528              'but hopefully',
   1529            })
   1530            return vim.api.nvim_win_get_cursor(0)
   1531          ]])
   1532 
   1533            eq({
   1534              'This should be the line we do not want',
   1535              'but hopefully the last one',
   1536            }, get_lines(0, -1, true))
   1537            -- cursor should stay at the same screen column
   1538            eq({ 1, 22 }, api.nvim_win_get_cursor(0))
   1539            -- immediate call to nvim_win_get_cursor should have returned the same position
   1540            eq({ 1, 22 }, cursor)
   1541            -- coladd should become 0
   1542            eq(0, fn.winsaveview().coladd)
   1543          end
   1544        )
   1545 
   1546        it(
   1547          'does not change cursor screen column when cursor is after eol and row range decreased',
   1548          function()
   1549            insert([[
   1550          This should be first
   1551          then there is a line we do not want
   1552          and one more
   1553          and finally the last one]])
   1554 
   1555            -- position cursor on 'e' in 'more'
   1556            api.nvim_win_set_cursor(0, { 3, 11 })
   1557            -- turn on virtualedit
   1558            command('set virtualedit=all')
   1559            -- move cursor after eol
   1560            fn.winrestview({ coladd = 28 })
   1561 
   1562            local cursor = exec_lua([[
   1563            vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, {
   1564              'the line we do not want',
   1565              'but hopefully',
   1566            })
   1567            return vim.api.nvim_win_get_cursor(0)
   1568          ]])
   1569 
   1570            eq({
   1571              'This should be the line we do not want',
   1572              'but hopefully the last one',
   1573            }, get_lines(0, -1, true))
   1574            -- cursor should end up at eol of a new row
   1575            eq({ 2, 26 }, api.nvim_win_get_cursor(0))
   1576            -- immediate call to nvim_win_get_cursor should have returned the same position
   1577            eq({ 2, 26 }, cursor)
   1578            -- coladd should be increased so that cursor stays in the same screen column
   1579            eq(13, fn.winsaveview().coladd)
   1580          end
   1581        )
   1582      end)
   1583    end)
   1584 
   1585    describe('when cursor is at end_row and after end_col', function()
   1586      it('adjusts cursor column when only a newline is added or deleted', function()
   1587        insert([[
   1588        first line
   1589        second
   1590         line]])
   1591 
   1592        -- position the cursor on 'i'
   1593        api.nvim_win_set_cursor(0, { 3, 2 })
   1594        set_text(1, 6, 2, 0, {})
   1595        eq({ 'first line', 'second line' }, get_lines(0, -1, true))
   1596        -- cursor should stay on 'i'
   1597        eq({ 2, 8 }, api.nvim_win_get_cursor(0))
   1598 
   1599        -- add a newline back
   1600        set_text(1, 6, 1, 6, { '', '' })
   1601        eq({ 'first line', 'second', ' line' }, get_lines(0, -1, true))
   1602        -- cursor should return back to the original position
   1603        eq({ 3, 2 }, api.nvim_win_get_cursor(0))
   1604      end)
   1605 
   1606      it(
   1607        'adjusts cursor column if the range is not bound to either start or end of a line',
   1608        function()
   1609          insert([[
   1610        This should be first
   1611        then there is a line we do not want
   1612        and finally the last one]])
   1613 
   1614          -- position the cursor on 'h' in 'the'
   1615          api.nvim_win_set_cursor(0, { 3, 13 })
   1616          set_text(0, 14, 2, 11, {})
   1617          eq({ 'This should be the last one' }, get_lines(0, -1, true))
   1618          -- cursor should stay on 'h'
   1619          eq({ 1, 16 }, api.nvim_win_get_cursor(0))
   1620          -- add deleted lines back
   1621          set_text(0, 14, 0, 14, {
   1622            ' first',
   1623            'then there is a line we do not want',
   1624            'and finally',
   1625          })
   1626          eq({
   1627            'This should be first',
   1628            'then there is a line we do not want',
   1629            'and finally the last one',
   1630          }, get_lines(0, -1, true))
   1631          -- cursor should return back to the original position
   1632          eq({ 3, 13 }, api.nvim_win_get_cursor(0))
   1633        end
   1634      )
   1635 
   1636      it(
   1637        'adjusts cursor column if replacing lines in range, not just deleting and adding',
   1638        function()
   1639          insert([[
   1640        This should be first
   1641        then there is a line we do not want
   1642        and finally the last one]])
   1643 
   1644          -- position the cursor on 's' in 'last'
   1645          api.nvim_win_set_cursor(0, { 3, 18 })
   1646          set_text(0, 15, 2, 11, {
   1647            'the line we do not want',
   1648            'but hopefully',
   1649          })
   1650 
   1651          eq({
   1652            'This should be the line we do not want',
   1653            'but hopefully the last one',
   1654          }, get_lines(0, -1, true))
   1655          -- cursor should stay on 's'
   1656          eq({ 2, 20 }, api.nvim_win_get_cursor(0))
   1657 
   1658          set_text(0, 15, 1, 13, {
   1659            'first',
   1660            'then there is a line we do not want',
   1661            'and finally',
   1662          })
   1663 
   1664          eq({
   1665            'This should be first',
   1666            'then there is a line we do not want',
   1667            'and finally the last one',
   1668          }, get_lines(0, -1, true))
   1669          -- cursor should return back to the original position
   1670          eq({ 3, 18 }, api.nvim_win_get_cursor(0))
   1671        end
   1672      )
   1673 
   1674      it('does not move cursor column after end of a line', function()
   1675        insert([[
   1676        This should be the only line here
   1677        ]])
   1678 
   1679        -- position cursor at the empty line
   1680        api.nvim_win_set_cursor(0, { 2, 0 })
   1681 
   1682        local cursor = exec_lua([[
   1683          vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'})
   1684          return vim.api.nvim_win_get_cursor(0)
   1685        ]])
   1686 
   1687        eq({ 'This should be the only line here!' }, get_lines(0, -1, true))
   1688        -- cursor should end up on '!'
   1689        eq({ 1, 33 }, api.nvim_win_get_cursor(0))
   1690        -- immediate call to nvim_win_get_cursor should have returned the same position
   1691        eq({ 1, 33 }, cursor)
   1692      end)
   1693 
   1694      it('does not move cursor column before start of a line', function()
   1695        insert('\n')
   1696 
   1697        eq({ '', '' }, get_lines(0, -1, true))
   1698 
   1699        -- position cursor on the last '1'
   1700        api.nvim_win_set_cursor(0, { 2, 2 })
   1701 
   1702        local cursor = exec_lua([[
   1703          vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''})
   1704          return vim.api.nvim_win_get_cursor(0)
   1705        ]])
   1706 
   1707        eq({ '' }, get_lines(0, -1, true))
   1708        -- cursor should end up on '!'
   1709        eq({ 1, 0 }, api.nvim_win_get_cursor(0))
   1710        -- immediate call to nvim_win_get_cursor should have returned the same position
   1711        eq({ 1, 0 }, cursor)
   1712      end)
   1713    end)
   1714 
   1715    it('can handle NULs', function()
   1716      set_text(0, 0, 0, 0, { 'ab\0cd' })
   1717      eq('ab\0cd', curbuf_depr('get_line', 0))
   1718    end)
   1719 
   1720    it('adjusts extmarks', function()
   1721      local ns = api.nvim_create_namespace('my-fancy-plugin')
   1722      insert([[
   1723      foo bar
   1724      baz
   1725      ]])
   1726      local id1 = api.nvim_buf_set_extmark(0, ns, 0, 1, {})
   1727      local id2 = api.nvim_buf_set_extmark(0, ns, 0, 7, {})
   1728      local id3 = api.nvim_buf_set_extmark(0, ns, 1, 1, {})
   1729      set_text(0, 4, 0, 7, { 'q' })
   1730 
   1731      eq({ 'foo q', 'baz' }, get_lines(0, 2, true))
   1732      -- mark before replacement point is unaffected
   1733      eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
   1734      -- mark gets shifted back because the replacement was shorter
   1735      eq({ 0, 5 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
   1736      -- mark on the next line is unaffected
   1737      eq({ 1, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
   1738 
   1739      -- replacing the text spanning two lines will adjust the mark on the next line
   1740      set_text(0, 3, 1, 3, { 'qux' })
   1741      eq({ 'fooqux', '' }, get_lines(0, 2, true))
   1742      eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
   1743      -- but mark before replacement point is still unaffected
   1744      eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
   1745      -- and the mark in the middle was shifted to the end of the insertion
   1746      eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
   1747 
   1748      -- marks should be put back into the same place after undoing
   1749      set_text(0, 0, 0, 2, { '' })
   1750      feed('u')
   1751      eq({ 0, 1 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
   1752      eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
   1753      eq({ 0, 6 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
   1754 
   1755      -- marks should be shifted over by the correct number of bytes for multibyte
   1756      -- chars
   1757      set_text(0, 0, 0, 0, { 'Ø' })
   1758      eq({ 0, 3 }, api.nvim_buf_get_extmark_by_id(0, ns, id1, {}))
   1759      eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id2, {}))
   1760      eq({ 0, 8 }, api.nvim_buf_get_extmark_by_id(0, ns, id3, {}))
   1761    end)
   1762 
   1763    it('correctly marks changed region for redraw #13890', function()
   1764      local screen = Screen.new(20, 5)
   1765 
   1766      insert([[
   1767      AAA
   1768      BBB
   1769      ]])
   1770 
   1771      api.nvim_buf_set_text(0, 0, 0, 1, 3, { 'XXX', 'YYY' })
   1772 
   1773      screen:expect([[
   1774        XXX                 |
   1775        YYY                 |
   1776        ^                    |
   1777        {1:~                   }|
   1778                            |
   1779      ]])
   1780    end)
   1781 
   1782    it('errors on out-of-range', function()
   1783      insert([[
   1784      hello foo!
   1785      text]])
   1786      eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {}))
   1787      eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {}))
   1788      eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {}))
   1789      eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {}))
   1790      eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {}))
   1791      eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {}))
   1792    end)
   1793 
   1794    it('errors when start is greater than end', function()
   1795      insert([[
   1796      hello foo!
   1797      text]])
   1798      eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {}))
   1799      eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {}))
   1800    end)
   1801 
   1802    it('no heap-use-after-free when called consecutively #19643', function()
   1803      set_text(0, 0, 0, 0, { 'one', '', '', 'two' })
   1804      eq({ 'one', '', '', 'two' }, get_lines(0, 4, true))
   1805      api.nvim_win_set_cursor(0, { 1, 0 })
   1806      exec_lua([[
   1807        vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
   1808        vim.api.nvim_buf_set_text(0, 0, 3, 1, 0, {''})
   1809      ]])
   1810      eq({ 'one', 'two' }, get_lines(0, 2, true))
   1811    end)
   1812 
   1813    it('auto-loads unloaded buffer', function()
   1814      local new_bufnr = fn.bufnr('set_text', true)
   1815      eq(false, api.nvim_buf_is_loaded(new_bufnr))
   1816      api.nvim_buf_set_text(new_bufnr, 0, 0, 0, -1, { 'foo' })
   1817      eq(true, api.nvim_buf_is_loaded(new_bufnr))
   1818      eq({ 'foo' }, api.nvim_buf_get_lines(new_bufnr, 0, -1, false))
   1819    end)
   1820 
   1821    describe('handles topline', function()
   1822      local screen
   1823      before_each(function()
   1824        screen = Screen.new(20, 12)
   1825        api.nvim_buf_set_lines(
   1826          0,
   1827          0,
   1828          -1,
   1829          true,
   1830          { 'aaa', 'bbb', 'ccc', 'ddd', 'www', 'xxx', 'yyy', 'zzz' }
   1831        )
   1832        api.nvim_set_option_value('modified', false, {})
   1833      end)
   1834 
   1835      it('of current window', function()
   1836        local win = api.nvim_get_current_win()
   1837        local buf = api.nvim_get_current_buf()
   1838 
   1839        command('new | wincmd w')
   1840        api.nvim_win_set_cursor(win, { 8, 0 })
   1841 
   1842        screen:expect {
   1843          grid = [[
   1844                              |
   1845          {1:~                   }|*4
   1846          {2:[No Name]           }|
   1847          www                 |
   1848          xxx                 |
   1849          yyy                 |
   1850          ^zzz                 |
   1851          {3:[No Name]           }|
   1852                              |
   1853        ]],
   1854        }
   1855        api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
   1856 
   1857        screen:expect {
   1858          grid = [[
   1859                              |
   1860          {1:~                   }|*4
   1861          {2:[No Name]           }|
   1862          www                 |
   1863          xxx                 |
   1864          yyy                 |
   1865          ^zzz                 |
   1866          {3:[No Name] [+]       }|
   1867                              |
   1868        ]],
   1869        }
   1870      end)
   1871 
   1872      it('of non-current window', function()
   1873        local win = api.nvim_get_current_win()
   1874        local buf = api.nvim_get_current_buf()
   1875 
   1876        command('new')
   1877        api.nvim_win_set_cursor(win, { 8, 0 })
   1878 
   1879        screen:expect {
   1880          grid = [[
   1881          ^                    |
   1882          {1:~                   }|*4
   1883          {3:[No Name]           }|
   1884          www                 |
   1885          xxx                 |
   1886          yyy                 |
   1887          zzz                 |
   1888          {2:[No Name]           }|
   1889                              |
   1890        ]],
   1891        }
   1892 
   1893        api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
   1894        screen:expect {
   1895          grid = [[
   1896          ^                    |
   1897          {1:~                   }|*4
   1898          {3:[No Name]           }|
   1899          www                 |
   1900          xxx                 |
   1901          yyy                 |
   1902          zzz                 |
   1903          {2:[No Name] [+]       }|
   1904                              |
   1905        ]],
   1906        }
   1907      end)
   1908 
   1909      it('of split windows with same buffer', function()
   1910        local win = api.nvim_get_current_win()
   1911        local buf = api.nvim_get_current_buf()
   1912 
   1913        command('split')
   1914        api.nvim_win_set_cursor(win, { 8, 0 })
   1915        api.nvim_win_set_cursor(0, { 1, 1 })
   1916 
   1917        screen:expect {
   1918          grid = [[
   1919          a^aa                 |
   1920          bbb                 |
   1921          ccc                 |
   1922          ddd                 |
   1923          www                 |
   1924          {3:[No Name]           }|
   1925          www                 |
   1926          xxx                 |
   1927          yyy                 |
   1928          zzz                 |
   1929          {2:[No Name]           }|
   1930                              |
   1931        ]],
   1932        }
   1933        api.nvim_buf_set_text(buf, 0, 3, 1, 0, { 'X' })
   1934 
   1935        screen:expect {
   1936          grid = [[
   1937          a^aaXbbb             |
   1938          ccc                 |
   1939          ddd                 |
   1940          www                 |
   1941          xxx                 |
   1942          {3:[No Name] [+]       }|
   1943          www                 |
   1944          xxx                 |
   1945          yyy                 |
   1946          zzz                 |
   1947          {2:[No Name] [+]       }|
   1948                              |
   1949        ]],
   1950        }
   1951      end)
   1952 
   1953      describe('of current window when', function()
   1954        before_each(function()
   1955          command('new | wincmd w | setlocal modified')
   1956          feed('Gk')
   1957          screen:expect([[
   1958                                |
   1959            {1:~                   }|*4
   1960            {2:[No Name]           }|
   1961            www                 |
   1962            xxx                 |
   1963            ^yyy                 |
   1964            zzz                 |
   1965            {3:[No Name] [+]       }|
   1966                                |
   1967          ]])
   1968        end)
   1969 
   1970        it('deleting 3 lines around topline', function()
   1971          api.nvim_buf_set_text(0, 3, 0, 6, 0, {})
   1972          screen:expect([[
   1973                                |
   1974            {1:~                   }|*4
   1975            {2:[No Name]           }|
   1976            ccc                 |
   1977            ^yyy                 |
   1978            zzz                 |
   1979            {1:~                   }|
   1980            {3:[No Name] [+]       }|
   1981                                |
   1982          ]])
   1983          eq(5, api.nvim_buf_line_count(0))
   1984        end)
   1985 
   1986        it('deleting 3 lines around the line just before topline', function()
   1987          api.nvim_buf_set_text(0, 2, 0, 5, 0, {})
   1988          screen:expect([[
   1989                                |
   1990            {1:~                   }|*4
   1991            {2:[No Name]           }|
   1992            bbb                 |
   1993            xxx                 |
   1994            ^yyy                 |
   1995            zzz                 |
   1996            {3:[No Name] [+]       }|
   1997                                |
   1998          ]])
   1999          eq(5, api.nvim_buf_line_count(0))
   2000        end)
   2001 
   2002        for count = 1, 4 do
   2003          it(('deleting %d lines just before topline'):format(count), function()
   2004            api.nvim_buf_set_text(0, 4 - count, 0, 4, 0, {})
   2005            screen:expect_unchanged()
   2006            eq(8 - count, api.nvim_buf_line_count(0))
   2007          end)
   2008 
   2009          describe(('replacing %d lines just before topline with 2 lines'):format(count), function()
   2010            it('including final newline', function()
   2011              api.nvim_buf_set_text(0, 4 - count, 0, 4, 0, { 'eee', 'fff', '' })
   2012              screen:expect_unchanged()
   2013              eq(8 - count + 2, api.nvim_buf_line_count(0))
   2014            end)
   2015 
   2016            it('excluding final newline', function()
   2017              api.nvim_buf_set_text(0, 4 - count, 0, 3, -1, { 'eee', 'fff' })
   2018              screen:expect_unchanged()
   2019              eq(8 - count + 2, api.nvim_buf_line_count(0))
   2020            end)
   2021          end)
   2022        end
   2023 
   2024        for count = 1, 3 do
   2025          it(('deleting %d lines far before topline'):format(count), function()
   2026            api.nvim_buf_set_text(0, 0, 0, count, 0, {})
   2027            screen:expect_unchanged()
   2028            eq(8 - count, api.nvim_buf_line_count(0))
   2029          end)
   2030 
   2031          describe(('replacing %d lines far before topline with 2 lines'):format(count), function()
   2032            it('including final newline', function()
   2033              api.nvim_buf_set_text(0, 0, 0, count, 0, { 'eee', 'fff', '' })
   2034              screen:expect_unchanged()
   2035              eq(8 - count + 2, api.nvim_buf_line_count(0))
   2036            end)
   2037 
   2038            it('excluding final newline', function()
   2039              api.nvim_buf_set_text(0, 0, 0, count - 1, -1, { 'eee', 'fff' })
   2040              screen:expect_unchanged()
   2041              eq(8 - count + 2, api.nvim_buf_line_count(0))
   2042            end)
   2043          end)
   2044        end
   2045 
   2046        describe('replacing topline', function()
   2047          describe('with 1 line', function()
   2048            local s1 = [[
   2049                                  |
   2050              {1:~                   }|*4
   2051              {2:[No Name]           }|
   2052              eee                 |
   2053              xxx                 |
   2054              ^yyy                 |
   2055              zzz                 |
   2056              {3:[No Name] [+]       }|
   2057                                  |
   2058            ]]
   2059            it('including final newline', function()
   2060              api.nvim_buf_set_text(0, 4, 0, 5, 0, { 'eee', '' })
   2061              screen:expect(s1)
   2062            end)
   2063            it('excluding final newline', function()
   2064              api.nvim_buf_set_text(0, 4, 0, 4, -1, { 'eee' })
   2065              screen:expect(s1)
   2066            end)
   2067          end)
   2068 
   2069          describe('with 2 lines', function()
   2070            local s2 = [[
   2071                                  |
   2072              {1:~                   }|*4
   2073              {2:[No Name]           }|
   2074              eee                 |
   2075              fff                 |
   2076              xxx                 |
   2077              ^yyy                 |
   2078              {3:[No Name] [+]       }|
   2079                                  |
   2080            ]]
   2081            it('including final newline', function()
   2082              api.nvim_buf_set_text(0, 4, 0, 5, 0, { 'eee', 'fff', '' })
   2083              screen:expect(s2)
   2084            end)
   2085            it('excluding final newline', function()
   2086              api.nvim_buf_set_text(0, 4, 0, 4, -1, { 'eee', 'fff' })
   2087              screen:expect(s2)
   2088            end)
   2089          end)
   2090        end)
   2091 
   2092        it('inserting at start of topline', function()
   2093          api.nvim_buf_set_text(0, 4, 0, 4, 0, { 'X', '' })
   2094          screen:expect([[
   2095                                |
   2096            {1:~                   }|*4
   2097            {2:[No Name]           }|
   2098            X                   |
   2099            www                 |
   2100            xxx                 |
   2101            ^yyy                 |
   2102            {3:[No Name] [+]       }|
   2103                                |
   2104          ]])
   2105        end)
   2106      end)
   2107    end)
   2108  end)
   2109 
   2110  describe_lua_and_rpc('nvim_buf_get_text', function(lua_or_rpc)
   2111    local get_text = lua_or_rpc.nvim_buf_get_text
   2112    before_each(function()
   2113      insert([[
   2114      hello foo!
   2115      text
   2116      more]])
   2117    end)
   2118 
   2119    it('works', function()
   2120      eq({ 'hello' }, get_text(0, 0, 0, 0, 5, {}))
   2121      eq({ 'hello foo!' }, get_text(0, 0, 0, 0, 42, {}))
   2122      eq({ 'foo!' }, get_text(0, 0, 6, 0, 10, {}))
   2123      eq({ 'foo!', 'tex' }, get_text(0, 0, 6, 1, 3, {}))
   2124      eq({ 'foo!', 'tex' }, get_text(0, -3, 6, -2, 3, {}))
   2125      eq({ '' }, get_text(0, 0, 18, 0, 20, {}))
   2126      eq({ 'ext' }, get_text(0, -2, 1, -2, 4, {}))
   2127      eq({ 'hello foo!', 'text', 'm' }, get_text(0, 0, 0, 2, 1, {}))
   2128      eq({ 'hello foo!' }, get_text(0, 0, -987654321, 0, 987654321, {}))
   2129      eq({ '' }, get_text(0, 0, -15, 0, -20, {}))
   2130    end)
   2131 
   2132    it('errors on out-of-range', function()
   2133      eq('Index out of bounds', pcall_err(get_text, 0, 2, 0, 4, 0, {}))
   2134      eq('Index out of bounds', pcall_err(get_text, 0, -4, 0, 0, 0, {}))
   2135      eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, 3, 0, {}))
   2136      eq('Index out of bounds', pcall_err(get_text, 0, 0, 0, -4, 0, {}))
   2137      -- no ml_get errors should happen #19017
   2138      eq('', api.nvim_get_vvar('errmsg'))
   2139    end)
   2140 
   2141    it('errors when start is greater than end', function()
   2142      eq("'start' is higher than 'end'", pcall_err(get_text, 0, 1, 0, 0, 0, {}))
   2143      eq('start_col must be less than or equal to end_col', pcall_err(get_text, 0, 0, 1, 0, 0, {}))
   2144    end)
   2145  end)
   2146 
   2147  describe('nvim_buf_get_offset', function()
   2148    local get_offset = api.nvim_buf_get_offset
   2149    it('works', function()
   2150      api.nvim_buf_set_lines(0, 0, -1, true, { 'Some\r', 'exa\000mple', '', 'buf\rfer', 'text' })
   2151      eq(5, api.nvim_buf_line_count(0))
   2152      eq(0, get_offset(0, 0))
   2153      eq(6, get_offset(0, 1))
   2154      eq(15, get_offset(0, 2))
   2155      eq(16, get_offset(0, 3))
   2156      eq(24, get_offset(0, 4))
   2157      eq(29, get_offset(0, 5))
   2158      eq('Index out of bounds', pcall_err(get_offset, 0, 6))
   2159      eq('Index out of bounds', pcall_err(get_offset, 0, -1))
   2160 
   2161      api.nvim_set_option_value('eol', false, {})
   2162      api.nvim_set_option_value('fixeol', false, {})
   2163      eq(28, get_offset(0, 5))
   2164 
   2165      -- fileformat is ignored
   2166      api.nvim_set_option_value('fileformat', 'dos', {})
   2167      eq(0, get_offset(0, 0))
   2168      eq(6, get_offset(0, 1))
   2169      eq(15, get_offset(0, 2))
   2170      eq(16, get_offset(0, 3))
   2171      eq(24, get_offset(0, 4))
   2172      eq(28, get_offset(0, 5))
   2173      api.nvim_set_option_value('eol', true, {})
   2174      eq(29, get_offset(0, 5))
   2175 
   2176      command('set hidden')
   2177      command('enew')
   2178      eq(6, api.nvim_buf_get_offset(1, 1))
   2179      command('bunload! 1')
   2180      eq(-1, api.nvim_buf_get_offset(1, 1))
   2181      eq(-1, api.nvim_buf_get_offset(1, 0))
   2182    end)
   2183 
   2184    it('works in empty buffer', function()
   2185      eq(0, get_offset(0, 0))
   2186      eq(1, get_offset(0, 1))
   2187      eq(-1, fn.line2byte('$'))
   2188    end)
   2189 
   2190    it('works in buffer with one line inserted', function()
   2191      feed('itext')
   2192      eq(0, get_offset(0, 0))
   2193      eq(5, get_offset(0, 1))
   2194    end)
   2195  end)
   2196 
   2197  describe('nvim_buf_get_var, nvim_buf_set_var, nvim_buf_del_var', function()
   2198    it('works', function()
   2199      api.nvim_buf_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } })
   2200      eq({ 1, 2, { ['3'] = 1 } }, api.nvim_buf_get_var(0, 'lua'))
   2201      eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('b:lua'))
   2202      eq(1, fn.exists('b:lua'))
   2203      api.nvim_buf_del_var(0, 'lua')
   2204      eq(0, fn.exists('b:lua'))
   2205      eq('Key not found: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua'))
   2206      api.nvim_buf_set_var(0, 'lua', 1)
   2207      command('lockvar b:lua')
   2208      eq('Key is locked: lua', pcall_err(api.nvim_buf_del_var, 0, 'lua'))
   2209      eq('Key is locked: lua', pcall_err(api.nvim_buf_set_var, 0, 'lua', 1))
   2210      eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_del_var, 0, 'changedtick'))
   2211      eq('Key is read-only: changedtick', pcall_err(api.nvim_buf_set_var, 0, 'changedtick', 1))
   2212    end)
   2213  end)
   2214 
   2215  describe('nvim_buf_get_changedtick', function()
   2216    it('works', function()
   2217      eq(2, api.nvim_buf_get_changedtick(0))
   2218      api.nvim_buf_set_lines(0, 0, 1, false, { 'abc\0', '\0def', 'ghi' })
   2219      eq(3, api.nvim_buf_get_changedtick(0))
   2220      eq(3, api.nvim_buf_get_var(0, 'changedtick'))
   2221    end)
   2222 
   2223    it('buffer_set_var returns the old value', function()
   2224      local val1 = { 1, 2, { ['3'] = 1 } }
   2225      local val2 = { 4, 7 }
   2226      eq(NIL, request('buffer_set_var', 0, 'lua', val1))
   2227      eq(val1, request('buffer_set_var', 0, 'lua', val2))
   2228    end)
   2229 
   2230    it('buffer_del_var returns the old value', function()
   2231      local val1 = { 1, 2, { ['3'] = 1 } }
   2232      local val2 = { 4, 7 }
   2233      eq(NIL, request('buffer_set_var', 0, 'lua', val1))
   2234      eq(val1, request('buffer_set_var', 0, 'lua', val2))
   2235      eq(val2, request('buffer_del_var', 0, 'lua'))
   2236    end)
   2237  end)
   2238 
   2239  describe('nvim_get_option_value, nvim_set_option_value', function()
   2240    it('works', function()
   2241      eq(8, api.nvim_get_option_value('shiftwidth', {}))
   2242      api.nvim_set_option_value('shiftwidth', 4, {})
   2243      eq(4, api.nvim_get_option_value('shiftwidth', {}))
   2244      -- global-local option
   2245      api.nvim_set_option_value('define', 'test', { buf = 0 })
   2246      eq('test', api.nvim_get_option_value('define', { buf = 0 }))
   2247      -- Doesn't change the global value
   2248      eq('', api.nvim_get_option_value('define', { scope = 'global' }))
   2249    end)
   2250 
   2251    it('returns values for unset local options', function()
   2252      -- 'undolevels' is only set to its "unset" value when a new buffer is
   2253      -- created
   2254      command('enew')
   2255      eq(-123456, api.nvim_get_option_value('undolevels', { buf = 0 }))
   2256    end)
   2257  end)
   2258 
   2259  describe('nvim_buf_get_name, nvim_buf_set_name', function()
   2260    it('works', function()
   2261      command('new')
   2262      eq('', api.nvim_buf_get_name(0))
   2263      local new_name = api.nvim_eval('resolve(tempname())')
   2264      api.nvim_buf_set_name(0, new_name)
   2265      eq(new_name, api.nvim_buf_get_name(0))
   2266      command('w!')
   2267      eq(1, fn.filereadable(new_name))
   2268      os.remove(new_name)
   2269    end)
   2270 
   2271    describe("with 'autochdir'", function()
   2272      local topdir
   2273      local oldbuf
   2274      local newbuf
   2275 
   2276      before_each(function()
   2277        command('set shellslash')
   2278        topdir = fn.getcwd()
   2279        t.mkdir(topdir .. '/Xacd')
   2280 
   2281        oldbuf = api.nvim_get_current_buf()
   2282        command('vnew')
   2283        newbuf = api.nvim_get_current_buf()
   2284        command('set autochdir')
   2285      end)
   2286 
   2287      after_each(function()
   2288        n.rmdir(topdir .. '/Xacd')
   2289      end)
   2290 
   2291      it('does not change cwd with non-current buffer', function()
   2292        api.nvim_buf_set_name(oldbuf, topdir .. '/Xacd/foo.txt')
   2293        eq(topdir, fn.getcwd())
   2294      end)
   2295 
   2296      it('changes cwd with current buffer', function()
   2297        api.nvim_buf_set_name(newbuf, topdir .. '/Xacd/foo.txt')
   2298        eq(topdir .. '/Xacd', fn.getcwd())
   2299      end)
   2300    end)
   2301  end)
   2302 
   2303  describe('nvim_buf_is_loaded', function()
   2304    it('works', function()
   2305      -- record our buffer number for when we unload it
   2306      local bufnr = api.nvim_get_current_buf()
   2307      -- api should report that the buffer is loaded
   2308      ok(api.nvim_buf_is_loaded(bufnr))
   2309      -- hide the current buffer by switching to a new empty buffer
   2310      -- Careful! we need to modify the buffer first or vim will just reuse it
   2311      api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'line1' })
   2312      command('hide enew')
   2313      -- confirm the buffer is hidden, but still loaded
   2314      local infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')')
   2315      eq(1, #infolist)
   2316      eq(1, infolist[1].hidden)
   2317      eq(1, infolist[1].loaded)
   2318      -- now force unload the buffer
   2319      command('bunload! ' .. bufnr)
   2320      -- confirm the buffer is unloaded
   2321      infolist = api.nvim_eval('getbufinfo(' .. bufnr .. ')')
   2322      eq(0, infolist[1].loaded)
   2323      -- nvim_buf_is_loaded() should also report the buffer as unloaded
   2324      eq(false, api.nvim_buf_is_loaded(bufnr))
   2325    end)
   2326  end)
   2327 
   2328  describe('nvim_buf_is_valid', function()
   2329    it('works', function()
   2330      command('new')
   2331      local b = api.nvim_get_current_buf()
   2332      ok(api.nvim_buf_is_valid(b))
   2333      command('bw!')
   2334      ok(not api.nvim_buf_is_valid(b))
   2335    end)
   2336  end)
   2337 
   2338  describe('nvim_buf_delete', function()
   2339    it('allows for just deleting', function()
   2340      command('new')
   2341      local b = api.nvim_get_current_buf()
   2342      ok(api.nvim_buf_is_valid(b))
   2343      api.nvim_buf_delete(b, {})
   2344      ok(not api.nvim_buf_is_loaded(b))
   2345      ok(not api.nvim_buf_is_valid(b))
   2346    end)
   2347 
   2348    it('allows for just unloading', function()
   2349      command('new')
   2350      local b = api.nvim_get_current_buf()
   2351      ok(api.nvim_buf_is_valid(b))
   2352      api.nvim_buf_delete(b, { unload = true })
   2353      ok(not api.nvim_buf_is_loaded(b))
   2354      ok(api.nvim_buf_is_valid(b))
   2355    end)
   2356  end)
   2357 
   2358  describe('nvim_buf_get_mark', function()
   2359    it('works', function()
   2360      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2361      api.nvim_win_set_cursor(0, { 3, 4 })
   2362      command('mark v')
   2363      eq({ 3, 0 }, api.nvim_buf_get_mark(0, 'v'))
   2364    end)
   2365  end)
   2366 
   2367  describe('nvim_buf_set_mark', function()
   2368    it('works with buffer local marks', function()
   2369      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2370      eq(true, api.nvim_buf_set_mark(0, 'z', 1, 1, {}))
   2371      eq({ 1, 1 }, api.nvim_buf_get_mark(0, 'z'))
   2372      eq({ 0, 1, 2, 0 }, fn.getpos("'z"))
   2373    end)
   2374    it('works with file/uppercase marks', function()
   2375      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2376      eq(true, api.nvim_buf_set_mark(0, 'Z', 3, 2, {}))
   2377      eq({ 3, 2 }, api.nvim_buf_get_mark(0, 'Z'))
   2378      eq({ api.nvim_get_current_buf(), 3, 3, 0 }, fn.getpos("'Z"))
   2379    end)
   2380    it('fails when invalid marks names are used', function()
   2381      eq(false, pcall(api.nvim_buf_set_mark, 0, '!', 1, 0, {}))
   2382      eq(false, pcall(api.nvim_buf_set_mark, 0, 'fail', 1, 0, {}))
   2383    end)
   2384    it('fails when invalid buffer number is used', function()
   2385      eq(false, pcall(api.nvim_buf_set_mark, 99, 'a', 1, 1, {}))
   2386    end)
   2387    it('auto-loads unloaded buffer', function()
   2388      local new_bufnr = fn.bufnr('set_mark', true)
   2389      eq(false, api.nvim_buf_is_loaded(new_bufnr))
   2390      eq(true, api.nvim_buf_set_mark(new_bufnr, 'A', 0, 0, {}))
   2391      eq(true, api.nvim_buf_is_loaded(new_bufnr))
   2392      eq({ 0, 0 }, api.nvim_buf_get_mark(new_bufnr, 'A'))
   2393    end)
   2394  end)
   2395 
   2396  describe('nvim_buf_del_mark', function()
   2397    it('works with buffer local marks', function()
   2398      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2399      api.nvim_buf_set_mark(0, 'z', 3, 1, {})
   2400      eq(true, api.nvim_buf_del_mark(0, 'z'))
   2401      eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'z'))
   2402    end)
   2403    it('works with file/uppercase marks', function()
   2404      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2405      api.nvim_buf_set_mark(0, 'Z', 3, 3, {})
   2406      eq(true, api.nvim_buf_del_mark(0, 'Z'))
   2407      eq({ 0, 0 }, api.nvim_buf_get_mark(0, 'Z'))
   2408    end)
   2409    it('returns false in marks not set in this buffer', function()
   2410      local abuf = api.nvim_create_buf(false, true)
   2411      api.nvim_buf_set_lines(abuf, -1, -1, true, { 'a', 'bit of', 'text' })
   2412      api.nvim_buf_set_mark(abuf, 'A', 2, 2, {})
   2413      eq(false, api.nvim_buf_del_mark(0, 'A'))
   2414      eq({ 2, 2 }, api.nvim_buf_get_mark(abuf, 'A'))
   2415    end)
   2416    it('returns false if mark was not deleted', function()
   2417      api.nvim_buf_set_lines(0, -1, -1, true, { 'a', 'bit of', 'text' })
   2418      api.nvim_buf_set_mark(0, 'z', 3, 1, {})
   2419      eq(true, api.nvim_buf_del_mark(0, 'z'))
   2420      eq(false, api.nvim_buf_del_mark(0, 'z')) -- Mark was already deleted
   2421    end)
   2422    it('fails when invalid marks names are used', function()
   2423      eq(false, pcall(api.nvim_buf_del_mark, 0, '!'))
   2424      eq(false, pcall(api.nvim_buf_del_mark, 0, 'fail'))
   2425    end)
   2426    it('fails when invalid buffer number is used', function()
   2427      eq(false, pcall(api.nvim_buf_del_mark, 99, 'a'))
   2428    end)
   2429  end)
   2430 end)