local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')

local clear = n.clear
local feed = n.feed
local insert = n.insert
local exec_lua = n.exec_lua
local exec = n.exec
local expect_events = t.expect_events
local api = n.api
local fn = n.fn
local command = n.command
local eq = t.eq
local assert_alive = n.assert_alive
local pcall_err = t.pcall_err

--- @return integer
local function setup_provider(code)
  return exec_lua([[
    local api = vim.api
    _G.ns1 = api.nvim_create_namespace "ns1"
  ]] .. (code or [[
    beamtrace = {}
    local function on_do(kind, ...)
      table.insert(beamtrace, {kind, ...})
    end
  ]]) .. [[
    api.nvim_set_decoration_provider(_G.ns1, {
      on_start = on_do; on_buf = on_do;
      on_win = on_do; on_line = on_do; on_range = on_do;
      on_end = on_do; _on_spell_nav = on_do;
    })
    return _G.ns1
  ]])
end

local function setup_screen(screen)
  screen:set_default_attr_ids {
    [1] = { bold = true, foreground = Screen.colors.Blue },
    [2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
    [3] = { foreground = Screen.colors.Brown },
    [4] = { foreground = Screen.colors.Blue1 },
    [5] = { foreground = Screen.colors.Magenta },
    [6] = { bold = true, foreground = Screen.colors.Brown },
    [7] = { background = Screen.colors.Gray90 },
    [8] = { bold = true, reverse = true },
    [9] = { reverse = true },
    [10] = { italic = true, background = Screen.colors.Magenta },
    [11] = { foreground = Screen.colors.Red, background = tonumber('0x005028') },
    [12] = { foreground = tonumber('0x990000') },
    [13] = { background = Screen.colors.LightBlue },
    [14] = { background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue },
    [15] = { special = Screen.colors.Blue, undercurl = true },
    [16] = { special = Screen.colors.Red, undercurl = true },
    [17] = { foreground = Screen.colors.Red },
    [18] = { bold = true, foreground = Screen.colors.SeaGreen },
    [19] = { bold = true },
  }
end

describe('decorations providers', function()
  local screen ---@type test.functional.ui.screen
  before_each(function()
    clear()
    screen = Screen.new(40, 8)
    setup_screen(screen)
  end)

  local mulholland = [[
    // just to see if there was an accident
    // on Mulholland Drive
    try_start();
    bufref_T save_buf;
    switch_buffer(&save_buf, buf);
    posp = getmark(mark, false);
    restore_buffer(&save_buf); ]]

  local function check_trace(expected)
    local actual = exec_lua [[ local b = beamtrace beamtrace = {} return b ]]
    expect_events(expected, actual, 'beam trace')
  end

  it('does not OOM when inserting, rather than appending, to the decoration provider vector', function()
    -- Add a dummy decoration provider with a larger ns id than what setup_provider() creates.
    -- This forces get_decor_provider() to insert into the providers vector,
    -- rather than append, which used to spin in an infinite loop allocating
    -- memory until nvim crashed/was killed.
    setup_provider([[
      local ns2 = api.nvim_create_namespace "ns2"
      api.nvim_set_decoration_provider(ns2, {})
    ]])
    assert_alive()
  end)

  it('leave a trace', function()
    insert(mulholland)

    setup_provider()

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start();                            |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false);            |
      restore_buffer(&save_buf);^              |
                                              |
    ]],
    }
    check_trace {
      { 'start', 4 },
      { 'win', 1000, 1, 0, 6 },
      { 'line', 1000, 1, 0 },
      { 'range', 1000, 1, 0, 0, 1, 0 },
      { 'line', 1000, 1, 1 },
      { 'range', 1000, 1, 1, 0, 2, 0 },
      { 'line', 1000, 1, 2 },
      { 'range', 1000, 1, 2, 0, 3, 0 },
      { 'line', 1000, 1, 3 },
      { 'range', 1000, 1, 3, 0, 4, 0 },
      { 'line', 1000, 1, 4 },
      { 'range', 1000, 1, 4, 0, 5, 0 },
      { 'line', 1000, 1, 5 },
      { 'range', 1000, 1, 5, 0, 6, 0 },
      { 'line', 1000, 1, 6 },
      { 'range', 1000, 1, 6, 0, 7, 0 },
      { 'end', 4 },
    }

    feed 'iü<esc>'
    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start();                            |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false);            |
      restore_buffer(&save_buf);^ü             |
                                              |
    ]],
    }
    check_trace {
      { 'start', 5 },
      { 'buf', 1, 5 },
      { 'win', 1000, 1, 0, 6 },
      { 'line', 1000, 1, 6 },
      { 'range', 1000, 1, 6, 0, 7, 0 },
      { 'end', 5 },
    }
  end)

  it('can have single provider', function()
    insert(mulholland)
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, line,
                             { end_line = line, end_col = line+1,
                               hl_group = hl,
                               ephemeral = true
                              })
        end
      end
    ]]

    screen:expect {
      grid = [[
      {2:/}/ just to see if there was an accident |
      /{2:/} on Mulholland Drive                  |
      tr{2:y}_start();                            |
      buf{2:r}ef_T save_buf;                      |
      swit{2:c}h_buffer(&save_buf, buf);          |
      posp {2:=} getmark(mark, false);            |
      restor{2:e}_buffer(&save_buf);^              |
                                              |
    ]],
    }
  end)

  it('can indicate spellchecked points', function()
    exec [[
    set spell
    set spelloptions=noplainbuffer
    syntax off
    ]]

    insert [[
    I am well written text.
    i am not capitalized.
    I am a speling mistakke.
    ]]

    setup_provider [[
      local ns = api.nvim_create_namespace "spell"
      beamtrace = {}
      local function on_do(kind, ...)
        if kind == 'win' or kind == 'spell' then
          api.nvim_buf_set_extmark(0, ns, 0, 0, {
            end_row = 2,
            end_col = 23,
            spell = true,
            priority = 20,
            ephemeral = true
          })
        end
        table.insert(beamtrace, {kind, ...})
      end
    ]]

    check_trace {
      { 'start', 5 },
      { 'win', 1000, 1, 0, 3 },
      { 'line', 1000, 1, 0 },
      { 'range', 1000, 1, 0, 0, 1, 0 },
      { 'line', 1000, 1, 1 },
      { 'range', 1000, 1, 1, 0, 2, 0 },
      { 'line', 1000, 1, 2 },
      { 'range', 1000, 1, 2, 0, 3, 0 },
      { 'line', 1000, 1, 3 },
      { 'range', 1000, 1, 3, 0, 4, 0 },
      { 'end', 5 },
    }

    feed 'gg0'

    screen:expect {
      grid = [[
      ^I am well written text.                 |
      {15:i} am not capitalized.                   |
      I am a {16:speling} {16:mistakke}.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }

    feed ']s'
    check_trace {
      { 'spell', 1000, 1, 1, 0, 1, -1 },
    }
    screen:expect {
      grid = [[
      I am well written text.                 |
      {15:^i} am not capitalized.                   |
      I am a {16:speling} {16:mistakke}.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }

    feed ']s'
    check_trace {
      { 'spell', 1000, 1, 2, 7, 2, -1 },
    }
    screen:expect {
      grid = [[
      I am well written text.                 |
      {15:i} am not capitalized.                   |
      I am a {16:^speling} {16:mistakke}.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }

    -- spell=false with higher priority does disable spell
    local ns = api.nvim_create_namespace 'spell'
    local id = api.nvim_buf_set_extmark(0, ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false })

    screen:expect {
      grid = [[
      I am well written text.                 |
      i am not capitalized.                   |
      I am a ^speling mistakke.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }

    feed ']s'
    screen:expect {
      grid = [[
      I am well written text.                 |
      i am not capitalized.                   |
      I am a ^speling mistakke.                |
                                              |
      {1:~                                       }|*3
      {17:search hit BOTTOM, continuing at TOP}    |
    ]],
    }
    command('echo ""')

    -- spell=false with lower priority doesn't disable spell
    api.nvim_buf_set_extmark(0, ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false })

    screen:expect {
      grid = [[
      I am well written text.                 |
      {15:i} am not capitalized.                   |
      I am a {16:^speling} {16:mistakke}.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }

    feed ']s'
    screen:expect {
      grid = [[
      I am well written text.                 |
      {15:i} am not capitalized.                   |
      I am a {16:speling} {16:^mistakke}.                |
                                              |
      {1:~                                       }|*3
                                              |
    ]],
    }
  end)

  it('can predefine highlights', function()
    screen:try_resize(40, 16)
    insert(mulholland)
    exec [[
      3
      set ft=c
      syntax on
      set number cursorline
      split
    ]]
    local ns1 = setup_provider()

    for k, v in pairs {
      LineNr = { italic = true, bg = 'Magenta' },
      Comment = { fg = '#FF0000', bg = 80 * 256 + 40 },
      CursorLine = { link = 'ErrorMsg' },
    } do
      api.nvim_set_hl(ns1, k, v)
    end

    screen:expect {
      grid = [[
      {3:  1 }{4:// just to see if there was an accid}|
      {3:    }{4:ent}                                 |
      {3:  2 }{4:// on Mulholland Drive}              |
      {6:  3 }{7:^try_start();                        }|
      {3:  4 }bufref_T save_buf;                  |
      {3:  5 }switch_buffer(&save_buf, buf);      |
      {3:  6 }posp = getmark(mark, {5:false});        |
      {8:[No Name] [+]                           }|
      {3:  2 }{4:// on Mulholland Drive}              |
      {6:  3 }{7:try_start();                        }|
      {3:  4 }bufref_T save_buf;                  |
      {3:  5 }switch_buffer(&save_buf, buf);      |
      {3:  6 }posp = getmark(mark, {5:false});        |
      {3:  7 }restore_buffer(&save_buf);          |
      {9:[No Name] [+]                           }|
                                              |
    ]],
    }

    api.nvim_set_hl_ns(ns1)
    screen:expect {
      grid = [[
      {10:  1 }{11:// just to see if there was an accid}|
      {10:    }{11:ent}                                 |
      {10:  2 }{11:// on Mulholland Drive}              |
      {6:  3 }{2:^try_start();                        }|
      {10:  4 }bufref_T save_buf;                  |
      {10:  5 }switch_buffer(&save_buf, buf);      |
      {10:  6 }posp = getmark(mark, {5:false});        |
      {8:[No Name] [+]                           }|
      {10:  2 }{11:// on Mulholland Drive}              |
      {6:  3 }{2:try_start();                        }|
      {10:  4 }bufref_T save_buf;                  |
      {10:  5 }switch_buffer(&save_buf, buf);      |
      {10:  6 }posp = getmark(mark, {5:false});        |
      {10:  7 }restore_buffer(&save_buf);          |
      {9:[No Name] [+]                           }|
                                              |
    ]],
    }

    exec_lua [[
      local api = vim.api
      local thewin = api.nvim_get_current_win()
      local ns2 = api.nvim_create_namespace 'ns2'
      api.nvim_set_decoration_provider (ns2, {
        on_win = function (_, win, buf)
          api.nvim_set_hl_ns_fast(win == thewin and _G.ns1 or ns2)
        end;
      })
    ]]
    screen:expect {
      grid = [[
      {10:  1 }{11:// just to see if there was an accid}|
      {10:    }{11:ent}                                 |
      {10:  2 }{11:// on Mulholland Drive}              |
      {6:  3 }{2:^try_start();                        }|
      {10:  4 }bufref_T save_buf;                  |
      {10:  5 }switch_buffer(&save_buf, buf);      |
      {10:  6 }posp = getmark(mark, {5:false});        |
      {8:[No Name] [+]                           }|
      {3:  2 }{4:// on Mulholland Drive}              |
      {6:  3 }{7:try_start();                        }|
      {3:  4 }bufref_T save_buf;                  |
      {3:  5 }switch_buffer(&save_buf, buf);      |
      {3:  6 }posp = getmark(mark, {5:false});        |
      {3:  7 }restore_buffer(&save_buf);          |
      {9:[No Name] [+]                           }|
                                              |
    ]],
    }
  end)

  it('can break an existing link', function()
    insert(mulholland)
    local ns1 = setup_provider()

    exec [[
      highlight OriginalGroup guifg='#990000'
      highlight link LinkGroup OriginalGroup
    ]]

    api.nvim_buf_set_virtual_text(0, 0, 2, { { '- not red', 'LinkGroup' } }, {})
    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start(); {12:- not red}                  |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false);            |
      restore_buffer(&save_buf);^              |
                                              |
    ]],
    }

    api.nvim_set_hl(ns1, 'LinkGroup', { fg = 'Blue' })
    api.nvim_set_hl_ns(ns1)

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start(); {4:- not red}                  |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false);            |
      restore_buffer(&save_buf);^              |
                                              |
    ]],
    }
  end)

  it("with 'default': do not break an existing link", function()
    insert(mulholland)
    local ns1 = setup_provider()

    exec [[
      highlight OriginalGroup guifg='#990000'
      highlight link LinkGroup OriginalGroup
    ]]

    api.nvim_buf_set_virtual_text(0, 0, 2, { { '- not red', 'LinkGroup' } }, {})
    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start(); {12:- not red}                  |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false);            |
      restore_buffer(&save_buf);^              |
                                              |
    ]],
    }

    api.nvim_set_hl(ns1, 'LinkGroup', { fg = 'Blue', default = true })
    api.nvim_set_hl_ns(ns1)
    feed 'k'

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start(); {12:- not red}                  |
      bufref_T save_buf;                      |
      switch_buffer(&save_buf, buf);          |
      posp = getmark(mark, false^);            |
      restore_buffer(&save_buf);              |
                                              |
    ]],
    }
  end)

  it('can have virtual text', function()
    insert(mulholland)
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'+', 'ErrorMsg'}};
            virt_text_pos='overlay';
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect {
      grid = [[
      {2:+}/ just to see if there was an accident |
      {2:+}/ on Mulholland Drive                  |
      {2:+}ry_start();                            |
      {2:+}ufref_T save_buf;                      |
      {2:+}witch_buffer(&save_buf, buf);          |
      {2:+}osp = getmark(mark, false);            |
      {2:+}estore_buffer(&save_buf);^              |
                                              |
    ]],
    }
  end)

  it('can have virtual text of the style: right_align', function()
    insert(mulholland)
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
            virt_text_pos='right_align';
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect {
      grid = [[
      // just to see if there was an acciden+{2: }|
      // on Mulholland Drive               +{2:  }|
      try_start();                        +{2:   }|
      bufref_T save_buf;                 +{2:    }|
      switch_buffer(&save_buf, buf);    +{2:     }|
      posp = getmark(mark, false);     +{2:      }|
      restore_buffer(&save_buf);^      +{2:       }|
                                              |
    ]],
    }
  end)

  it('can have virtual text of the style: eol_right_align', function()
    insert(mulholland)
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}};
            virt_text_pos='eol_right_align';
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive       +{2:1234567890}|
      try_start();                 +{2:1234567890}|
      bufref_T save_buf;           +{2:1234567890}|
      switch_buffer(&save_buf, buf); +{2:12345678}|
      posp = getmark(mark, false); +{2:1234567890}|
      restore_buffer(&save_buf);^   +{2:1234567890}|
                                              |
    ]],
    }
  end)

  it('multiple eol_right_align', function()
    insert(mulholland)
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'11111'}};
            virt_text_pos='eol_right_align';
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(0, test_ns, line, 0, {
            virt_text = {{'22222'}};
            virt_text_pos='eol_right_align';
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive       11111 22222|
      try_start();                 11111 22222|
      bufref_T save_buf;           11111 22222|
      switch_buffer(&save_buf, buf); 11111 222|
      posp = getmark(mark, false); 11111 22222|
      restore_buffer(&save_buf);^   11111 22222|
                                              |
    ]],
    }
  end)

  it('eol_right_align: second text much longer than first', function()
    insert('short')
    setup_provider [[
      local test_ns = api.nvim_create_namespace "test_length_diff"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...

          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'AA', 'Comment'}};
            virt_text_pos = 'eol_right_align';
            priority = 100;
            ephemeral = true;
          })

          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{'BBBBBBBBBBBBBBBBBBBB', 'ErrorMsg'}};
            virt_text_pos = 'eol_right_align';
            priority = 200;
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect([[
      shor^t            {4:AA} {2:BBBBBBBBBBBBBBBBBBBB}|
      {1:~                                       }|*6
                                              |
    ]])
  end)

  it('virtual text works with wrapped lines', function()
    insert(mulholland)
    feed('ggJj3JjJ')
    setup_provider [[
      local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
      local test_ns = api.nvim_create_namespace "mulholland"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
            virt_text = {{string.rep('/', line+1), 'ErrorMsg'}};
            virt_text_pos='eol';
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(buf, test_ns, line, 6, {
            virt_text = {{string.rep('*', line+1), 'ErrorMsg'}};
            virt_text_pos='overlay';
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(buf, test_ns, line, 39, {
            virt_text = {{string.rep('!', line+1), 'ErrorMsg'}};
            virt_text_win_col=20;
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
            virt_text = {{string.rep('?', line+1), 'ErrorMsg'}};
            virt_text_win_col=10;
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
            virt_text = {{string.rep(';', line+1), 'ErrorMsg'}};
            virt_text_pos='overlay';
            ephemeral = true;
          })
          api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
            virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
            virt_text_pos='right_align';
            ephemeral = true;
          })
        end
      end
    ]]

    screen:expect {
      grid = [[
      // jus{2:*} to see if th{2:!}re was an accident |
      {2:;}n Mulholl{2:?}nd Drive {2:/}                 +{2: }|
      try_st{2:**}t(); bufref_{2:!!}save_buf; switch_b|
      {2:;;}fer(&sav{2:??}buf, buf); {2://}            +{2:  }|
      posp ={2:***}tmark(mark,{2:!!!}lse);^ restore_buf|
      {2:;;;}(&save_{2:???});  {2:///}                +{2:   }|
      {1:~                                       }|
                                              |
    ]],
    }
    command('setlocal breakindent breakindentopt=shift:2')
    screen:expect {
      grid = [[
      // jus{2:*} to see if th{2:!}re was an accident |
        {2:;}n Mulho{2:?}land Drive {2:/}               +{2: }|
      try_st{2:**}t(); bufref_{2:!!}save_buf; switch_b|
        {2:;;}fer(&s{2:??}e_buf, buf); {2://}          +{2:  }|
      posp ={2:***}tmark(mark,{2:!!!}lse);^ restore_buf|
        {2:;;;}(&sav{2:???}uf);  {2:///}              +{2:   }|
      {1:~                                       }|
                                              |
    ]],
    }
  end)

  it('can highlight beyond EOL', function()
    insert(mulholland)
    setup_provider [[
      local test_ns = api.nvim_create_namespace "veberod"
      function on_do(event, ...)
        if event == "line" then
          local win, buf, line = ...
          if string.find(api.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then
            api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
              end_line = line+1;
              hl_group = 'DiffAdd';
              hl_eol = true;
              ephemeral = true;
            })
          end
        end
      end
    ]]

    screen:expect {
      grid = [[
      // just to see if there was an accident |
      // on Mulholland Drive                  |
      try_start();                            |
      {13:bufref_T save_buf;                      }|
      {13:switch_buffer(&save_buf, buf);          }|
      posp = getmark(mark, false);            |
      {13:restore_buffer(&save_buf);^              }|
                                              |
    ]],
    }
  end)

  it('can create and remove signs when CursorMoved autocommand validates botline #18661', function()
    exec_lua([[
      local lines = {}
      for i = 1, 200 do
        lines[i] = 'hello' .. tostring(i)
      end
      vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
    ]])
    setup_provider([[
      local function on_do(kind, winid, bufnr, topline, botline)
        if kind == 'win' then
          if topline < 100 and botline > 100 then
            api.nvim_buf_set_extmark(bufnr, ns1, 99, -1, { sign_text = 'X' })
          else
            api.nvim_buf_clear_namespace(bufnr, ns1, 0, -1)
          end
        end
      end
    ]])
    command([[autocmd CursorMoved * call line('w$')]])
    api.nvim_win_set_cursor(0, { 100, 0 })
    screen:expect([[
      {14:  }hello97                               |
      {14:  }hello98                               |
      {14:  }hello99                               |
      {14:X }^hello100                              |
      {14:  }hello101                              |
      {14:  }hello102                              |
      {14:  }hello103                              |
                                              |
    ]])
    api.nvim_win_set_cursor(0, { 1, 0 })
    screen:expect([[
      ^hello1                                  |
      hello2                                  |
      hello3                                  |
      hello4                                  |
      hello5                                  |
      hello6                                  |
      hello7                                  |
                                              |
    ]])
  end)

  it('does allow removing extmarks during on_line callbacks', function()
    exec_lua([[
      eok = true
    ]])
    setup_provider([[
      local function on_do(kind, winid, bufnr, topline, botline)
        if kind == 'line' then
          api.nvim_buf_set_extmark(bufnr, ns1, 1, -1, { sign_text = 'X' })
          eok = pcall(api.nvim_buf_clear_namespace, bufnr, ns1, 0, -1)
        end
      end
    ]])
    exec_lua([[
      assert(eok == true)
    ]])
  end)

  it('on_line is invoked only for buffer lines', function()
    insert(mulholland)
    command('vnew')
    insert(mulholland)
    feed('dd')
    command('windo diffthis')

    exec_lua([[
      out_of_bound = false
    ]])
    setup_provider([[
      local function on_do(kind, _, bufnr, row)
        if kind == 'line' then
          if not api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] then
            out_of_bound = true
          end
        end
      end
    ]])

    feed('<C-e>')

    exec_lua([[
      assert(out_of_bound == false)
    ]])
  end)

  it('on_range is invoked on all visible characters', function()
    clear()
    screen = Screen.new(20, 4)
    setup_screen(screen)

    local function record()
      exec_lua(function()
        _G.p_min = { math.huge, math.huge }
        _G.p_max = { -math.huge, -math.huge }
        function _G.pos_gt(a, b)
          return a[1] > b[1] or (a[1] == b[1] and a[2] > b[2])
        end
        function _G.pos_lt(a, b)
          return a[1] < b[1] or (a[1] == b[1] and a[2] < b[2])
        end
      end)
      setup_provider [[
        local function on_do(kind, _, bufnr, br, bc, er, ec)
          if kind == 'range' then
            local b = { br, bc }
            local e = { er, ec }
            if _G.pos_gt(_G.p_min, b) then
              _G.p_min = b
            end
            if _G.pos_lt(_G.p_max, e) then
              _G.p_max = e
            end
          end
        end
      ]]
    end
    local function check(min, max)
      local p_min = exec_lua('return _G.p_min')
      assert(
        p_min[1] < min[1] or (p_min[1] == min[1] and p_min[2] <= min[2]),
        'minimum position ' .. vim.inspect(p_min) .. ' should be before the first char'
      )
      local p_max = exec_lua('return _G.p_max')
      assert(
        p_max[1] > max[1] or (p_max[1] == max[1] and p_max[2] >= max[2]),
        'maximum position ' .. vim.inspect(p_max) .. ' should be on or after the last char'
      )
    end

    -- Multiple lines.
    exec_lua([[
      local lines = { ('a'):rep(40), ('b'):rep(40), ('c'):rep(40) }
      vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      vim.api.nvim_win_set_cursor(0, { 2, 0 })
    ]])
    record()
    screen:expect([[
      ^bbbbbbbbbbbbbbbbbbbb|
      bbbbbbbbbbbbbbbbbbbb|
      ccccccccccccccccc{1:@@@}|
                          |
    ]])
    check({ 1, 0 }, { 2, 21 })

    -- One long line.
    exec_lua([[
      local lines = { ('a'):rep(100) }
      vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      vim.api.nvim_win_set_cursor(0, { 1, 70 })
    ]])
    record()
    screen:expect([[
      {1:<<<}aaaaaaaaaaaaaaaaa|
      aaaaaaaaaaaaaaaaaaaa|
      aaaaaaaaaa^aaaaaaaaaa|
                          |
    ]])
    check({ 0, 20 }, { 0, 81 })

    -- Multibyte characters.
    exec_lua([[
      local lines = { ('\195\162'):rep(100) }
      vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      vim.api.nvim_win_set_cursor(0, { 1, 70 * 2 })
    ]])
    record()
    screen:expect([[
      {1:<<<}âââââââââââââââââ|
      ââââââââââââââââââââ|
      ââââââââââ^ââââââââââ|
                          |
    ]])
    check({ 0, 20 * 2 }, { 0, 81 * 2 })

    -- Tabs.
    exec_lua([[
      local lines = { 'a' .. ('\t'):rep(100) }
      vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      vim.api.nvim_win_set_cursor(0, { 1, 39 })
    ]])
    record()
    screen:expect([[
      {1:<<<}                 |
                          |
                 ^         |
                          |
    ]])
    check({ 0, 33 }, { 0, 94 })

    -- One long line without wrapping.
    command('set nowrap')
    exec_lua([[
      local lines = { ('a'):rep(50) .. ('b'):rep(50) }
      vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
      vim.api.nvim_win_set_cursor(0, { 1, 50 })
    ]])
    record()
    screen:expect([[
      aaaaaaaaaa^bbbbbbbbbb|
      {1:~                   }|*2
                          |
    ]])
    check({ 0, 40 }, { 0, 60 })
  end)

  it('can add new providers during redraw #26652', function()
    setup_provider [[
    local ns = api.nvim_create_namespace('test_no_add')
    function on_do(...)
      api.nvim_set_decoration_provider(ns, {})
    end
    ]]

    n.assert_alive()
  end)

  it('is not invoked repeatedly in Visual mode with vim.schedule() #20235', function()
    exec_lua([[_G.cnt = 0]])
    setup_provider([[
      function on_do(event, ...)
        if event == 'win' then
          vim.schedule(function() end)
          _G.cnt = _G.cnt + 1
        end
      end
    ]])
    feed('v')
    screen:expect([[
      ^                                        |
      {1:~                                       }|*6
      {19:-- VISUAL --}                            |
    ]])
    eq(2, exec_lua([[return _G.cnt]]))
  end)

  it('can do large changes to the marktree', function()
    insert('line1 with a lot of text\nline2 with a lot of text')
    setup_provider([[
      function on_do(event, _, _, row)
        if event == 'win' or (event == 'line' and row == 1) then
          vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1)
          for i = 0,1 do
            for j = 0,23 do
              vim.api.nvim_buf_set_extmark(0, ns1, i, j, {hl_group='ErrorMsg', end_col = j+1})
            end
          end
        end
      end
    ]])

    -- Doesn't crash when modifying the marktree between line1 and line2
    screen:expect([[
      {2:line1 with a lot of text}                |
      {2:line2 with a lot of tex^t}                |
      {1:~                                       }|*5
                                              |
    ]])
  end)

  it('inline virt_text does not result in wrong cursor column #33153', function()
    insert('line1 with a lot of text\nline2 with a lot of text')
    setup_provider([[
      _G.do_win = false
      vim.keymap.set('n', 'f', function()
        _G.do_win = true
        vim.cmd('redraw!')
      end)
      vim.o.concealcursor = 'n'
      function on_do(event)
        if event == 'win' and _G.do_win then
          vim.api.nvim_buf_set_extmark(0, ns1, 1, 0, {
            virt_text = { { 'virt_text ' } },
            virt_text_pos = 'inline'
          })
        end
      end
    ]])
    feed('f')
    screen:expect([[
      line1 with a lot of text                |
      virt_text line2 with a lot of tex^t      |
      {1:~                                       }|*5
                                              |
    ]])
  end)

  it('decor provider is enabled again for next redraw after on_win disabled it', function()
    exec_lua(function()
      vim.api.nvim_set_decoration_provider(vim.api.nvim_create_namespace(''), {
        on_win = function()
          return false
        end,
        on_buf = function()
          _G.did_buf = (_G.did_buf or 0) + 1
        end,
      })
    end)
    api.nvim_buf_set_lines(0, 0, -1, false, { 'foo' })
    screen:expect([[
      ^foo                                     |
      {1:~                                       }|*6
                                              |
    ]])
    eq(1, exec_lua('return _G.did_buf'))
  end)
end)

describe('decoration_providers', function()
  it('errors and logs gracefully', function()
    local testlog = 'Xtest_decorations_log'
    clear({ env = { NVIM_LOG_FILE = testlog } })
    local screen = Screen.new(65, 7)
    setup_provider([[
      function on_do(...)
        error "Foo"
      end
    ]])
    screen:expect([[
      {3:                                                                 }|
      {9:Decoration provider "start" (ns=ns1):}                            |
      {9:Lua: [string "<nvim>"]:4: Foo}                                    |
      {9:stack traceback:}                                                 |
      {9:        [C]: in function 'error'}                                 |
      {9:        [string "<nvim>"]:4: in function <[string "<nvim>"]:3>}   |
      {6:Press ENTER or type command to continue}^                          |
    ]])
    t.assert_log('Error in decoration provider "start" %(ns=ns1%):', testlog, 100)
    t.assert_log('Lua: %[string "<nvim>"%]:4: Foo', testlog, 100)
    n.check_close()
    os.remove(testlog)
  end)
end)

local example_text = [[
for _,item in ipairs(items) do
    local text, hl_id_cell, count = unpack(item)
    if hl_id_cell ~= nil then
        hl_id = hl_id_cell
    end
    for _ = 1, (count or 1) do
        local cell = line[colpos]
        cell.text = text
        cell.hl_id = hl_id
        colpos = colpos+1
    end
end]]

describe('extmark decorations', function()
  local screen ---@type test.functional.ui.screen
  local ns ---@type integer
  before_each(function()
    clear()
    screen = Screen.new(50, 15)
    screen:set_default_attr_ids {
      [1] = { bold = true, foreground = Screen.colors.Blue },
      [2] = { foreground = Screen.colors.Brown },
      [3] = { bold = true, foreground = Screen.colors.SeaGreen },
      [4] = { background = Screen.colors.Red1, foreground = Screen.colors.Gray100 },
      [5] = { foreground = Screen.colors.Brown, bold = true },
      [6] = { foreground = Screen.colors.DarkCyan },
      [7] = { foreground = Screen.colors.Grey0, background = tonumber('0xff4c4c') },
      [8] = { foreground = tonumber('0x180606'), background = tonumber('0xff4c4c') },
      [9] = { foreground = tonumber('0xe40c0c'), background = tonumber('0xff4c4c'), bold = true },
      [10] = { foreground = tonumber('0xb20000'), background = tonumber('0xff4c4c') },
      [11] = { blend = 30, background = Screen.colors.Red1 },
      [12] = { foreground = Screen.colors.Brown, blend = 30, background = Screen.colors.Red1, bold = true },
      [13] = { foreground = Screen.colors.Fuchsia },
      [14] = { background = Screen.colors.Red1, foreground = Screen.colors.Black },
      [15] = { background = Screen.colors.Red1, foreground = tonumber('0xb20000') },
      [16] = { blend = 30, background = Screen.colors.Red1, foreground = Screen.colors.Magenta1 },
      [17] = { bold = true, foreground = Screen.colors.Brown, background = Screen.colors.LightGrey },
      [18] = { background = Screen.colors.LightGrey },
      [19] = { foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGrey },
      [20] = { foreground = tonumber('0x180606'), background = tonumber('0xf13f3f') },
      [21] = { foreground = Screen.colors.Gray0, background = tonumber('0xf13f3f') },
      [22] = { foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f') },
      [23] = { foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey },
      [24] = { bold = true },
      [25] = { background = Screen.colors.LightRed },
      [26] = { background = Screen.colors.DarkGrey, foreground = Screen.colors.LightGrey },
      [27] = { background = Screen.colors.LightGrey, foreground = Screen.colors.Black },
      [28] = { underline = true, foreground = Screen.colors.SlateBlue },
      [29] = { foreground = Screen.colors.SlateBlue, background = Screen.colors.LightGrey, underline = true },
      [30] = { foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGrey, underline = true },
      [31] = { underline = true, foreground = Screen.colors.DarkCyan },
      [32] = { underline = true },
      [33] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey },
      [34] = { background = Screen.colors.Yellow },
      [35] = { background = Screen.colors.Yellow, bold = true, foreground = Screen.colors.Blue },
      [36] = { foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.Red },
      [37] = { background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue },
      [38] = { background = Screen.colors.LightBlue },
      [39] = { foreground = Screen.colors.Blue1, background = Screen.colors.LightCyan1, bold = true },
      [40] = { reverse = true },
      [41] = { bold = true, reverse = true },
      [42] = { undercurl = true, special = Screen.colors.Red },
      [43] = { background = Screen.colors.Yellow, undercurl = true, special = Screen.colors.Red },
      [44] = { background = Screen.colors.LightMagenta },
      [45] = { background = Screen.colors.Red, special = Screen.colors.Red, foreground = Screen.colors.Red },
      [46] = { background = Screen.colors.Blue, foreground = Screen.colors.Blue, special = Screen.colors.Red },
      [47] = { background = Screen.colors.Green, foreground = Screen.colors.Blue, special = Screen.colors.Red },
    }

    ns = api.nvim_create_namespace 'test'
  end)

  it('empty virtual text at eol should not break colorcolumn #17860', function()
    insert(example_text)
    feed('gg')
    command('set colorcolumn=40')
    screen:expect([[
      ^for _,item in ipairs(items) do         {25: }          |
          local text, hl_id_cell, count = unp{25:a}ck(item)  |
          if hl_id_cell ~= nil then          {25: }          |
              hl_id = hl_id_cell             {25: }          |
          end                                {25: }          |
          for _ = 1, (count or 1) do         {25: }          |
              local cell = line[colpos]      {25: }          |
              cell.text = text               {25: }          |
              cell.hl_id = hl_id             {25: }          |
              colpos = colpos+1              {25: }          |
          end                                {25: }          |
      end                                    {25: }          |
      {1:~                                                 }|*2
                                                        |
    ]])
    api.nvim_buf_set_extmark(0, ns, 4, 0, { virt_text = { { '' } }, virt_text_pos = 'eol' })
    screen:expect_unchanged()
  end)

  it('can have virtual text of overlay position', function()
    insert(example_text)
    feed 'gg'

    for i = 1, 9 do
      api.nvim_buf_set_extmark(0, ns, i, 0, { virt_text = { { '|', 'LineNr' } }, virt_text_pos = 'overlay' })
      if i == 3 or (i >= 6 and i <= 9) then
        api.nvim_buf_set_extmark(0, ns, i, 4, { virt_text = { { '|', 'NonText' } }, virt_text_pos = 'overlay' })
      end
    end
    api.nvim_buf_set_extmark(
      0,
      ns,
      9,
      10,
      { virt_text = { { 'foo' }, { 'bar', 'MoreMsg' }, { '!!', 'ErrorMsg' } }, virt_text_pos = 'overlay' }
    )

    -- can "float" beyond end of line
    api.nvim_buf_set_extmark(0, ns, 5, 28, { virt_text = { { 'loopy', 'ErrorMsg' } }, virt_text_pos = 'overlay' })
    -- bound check: right edge of window
    api.nvim_buf_set_extmark(
      0,
      ns,
      2,
      26,
      { virt_text = { { 'bork bork bork' }, { (' bork'):rep(10), 'ErrorMsg' } }, virt_text_pos = 'overlay' }
    )
    -- empty virt_text should not change anything
    api.nvim_buf_set_extmark(0, ns, 6, 16, { virt_text = { { '' } }, virt_text_pos = 'overlay' })

    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
      {2:|}   local text, hl_id_cell, count = unpack(item)  |
      {2:|}   if hl_id_cell ~= nil tbork bork bork{4: bork bork}|
      {2:|}   {1:|}   hl_id = hl_id_cell                        |
      {2:|}   end                                           |
      {2:|}   for _ = 1, (count or 1) {4:loopy}                 |
      {2:|}   {1:|}   local cell = line[colpos]                 |
      {2:|}   {1:|}   cell.text = text                          |
      {2:|}   {1:|}   cell.hl_id = hl_id                        |
      {2:|}   {1:|}   cofoo{3:bar}{4:!!}olpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*2
                                                        |
    ]],
    }

    -- handles broken lines
    screen:try_resize(22, 25)
    screen:expect {
      grid = [[
      ^for _,item in ipairs(i|
      tems) do              |
      {2:|}   local text, hl_id_|
      cell, count = unpack(i|
      tem)                  |
      {2:|}   if hl_id_cell ~= n|
      il tbork bork bork{4: bor}|
      {2:|}   {1:|}   hl_id = hl_id_|
      cell                  |
      {2:|}   end               |
      {2:|}   for _ = 1, (count |
      or 1) {4:loopy}           |
      {2:|}   {1:|}   local cell = l|
      ine[colpos]           |
      {2:|}   {1:|}   cell.text = te|
      xt                    |
      {2:|}   {1:|}   cell.hl_id = h|
      l_id                  |
      {2:|}   {1:|}   cofoo{3:bar}{4:!!}olpo|
      s+1                   |
          end               |
      end                   |
      {1:~                     }|*2
                            |
    ]],
    }

    -- truncating in the middle of a char leaves a space
    api.nvim_buf_set_lines(0, 0, 1, true, { 'for _,item in ipairs(items) do  -- 古古古' })
    api.nvim_buf_set_lines(0, 10, 12, true, { '    end  -- ??????????', 'end  -- ?古古古古?古古' })
    api.nvim_buf_set_extmark(0, ns, 0, 35, { virt_text = { { 'A', 'ErrorMsg' }, { 'AA' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 10, 19, { virt_text = { { '口口口', 'ErrorMsg' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 11, 21, { virt_text = { { '口口口', 'ErrorMsg' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 11, 8, { virt_text = { { '口口', 'ErrorMsg' } }, virt_text_pos = 'overlay' })
    screen:expect {
      grid = [[
      ^for _,item in ipairs(i|
      tems) do  -- {4:A}AA 古   |
      {2:|}   local text, hl_id_|
      cell, count = unpack(i|
      tem)                  |
      {2:|}   if hl_id_cell ~= n|
      il tbork bork bork{4: bor}|
      {2:|}   {1:|}   hl_id = hl_id_|
      cell                  |
      {2:|}   end               |
      {2:|}   for _ = 1, (count |
      or 1) {4:loopy}           |
      {2:|}   {1:|}   local cell = l|
      ine[colpos]           |
      {2:|}   {1:|}   cell.text = te|
      xt                    |
      {2:|}   {1:|}   cell.hl_id = h|
      l_id                  |
      {2:|}   {1:|}   cofoo{3:bar}{4:!!}olpo|
      s+1                   |
          end  -- ???????{4:口 }|
      end  -- {4:口口} 古古{4:口口 }|
      {1:~                     }|*2
                            |
    ]],
    }

    screen:try_resize(82, 13)
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do  -- {4:A}AA 古                                         |
      {2:|}   local text, hl_id_cell, count = unpack(item)                                  |
      {2:|}   if hl_id_cell ~= nil tbork bork bork{4: bork bork bork bork bork bork bork bork b}|
      {2:|}   {1:|}   hl_id = hl_id_cell                                                        |
      {2:|}   end                                                                           |
      {2:|}   for _ = 1, (count or 1) {4:loopy}                                                 |
      {2:|}   {1:|}   local cell = line[colpos]                                                 |
      {2:|}   {1:|}   cell.text = text                                                          |
      {2:|}   {1:|}   cell.hl_id = hl_id                                                        |
      {2:|}   {1:|}   cofoo{3:bar}{4:!!}olpos+1                                                         |
          end  -- ???????{4:口口口}                                                         |
      end  -- {4:口口} 古古{4:口口口}                                                           |
                                                                                        |
    ]],
    }

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do  -- 古古古                                         |
          local text, hl_id_cell, count = unpack(item)                                  |
          if hl_id_cell ~= nil then                                                     |
              hl_id = hl_id_cell                                                        |
          end                                                                           |
          for _ = 1, (count or 1) do                                                    |
              local cell = line[colpos]                                                 |
              cell.text = text                                                          |
              cell.hl_id = hl_id                                                        |
              colpos = colpos+1                                                         |
          end  -- ??????????                                                            |
      end  -- ?古古古古?古古                                                            |
                                                                                        |
    ]],
    }
  end)

  it('overlay virtual text works with wrapped lines #25158', function()
    screen:try_resize(50, 6)
    insert(('ab'):rep(100))
    for i = 0, 9 do
      api.nvim_buf_set_extmark(0, ns, 0, 42 + i, { virt_text = { { tostring(i), 'ErrorMsg' } }, virt_text_pos = 'overlay' })
      api.nvim_buf_set_extmark(
        0,
        ns,
        0,
        91 + i,
        { virt_text = { { tostring(i), 'ErrorMsg' } }, virt_text_pos = 'overlay', virt_text_hide = true }
      )
    end
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {4:89}abababababababababababababababababababa{4:012345678}|
      {4:9}babababababababababababababababababababababababab|
      ababababababababababababababababababababababababa^b|
      {1:~                                                 }|
                                                        |
    ]],
    }

    command('set showbreak=++')
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
      {1:++}{4:789}babababababababababababababababababababababab|
      {1:++}abababababababababababababababababababababababab|
      {1:++}ababa^b                                          |
                                                        |
    ]],
    }

    feed('2gkvg0')
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
      {1:++}^a{27:babab}ababababababababababababababababababababab|
      {1:++}abababababababababababababababababababababababab|
      {1:++}ababab                                          |
      {24:-- VISUAL --}                                      |
    ]],
    }

    feed('o')
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
      {1:++}{27:ababa}^bababababababababababababababababababababab|
      {1:++}abababababababababababababababababababababababab|
      {1:++}ababab                                          |
      {24:-- VISUAL --}                                      |
    ]],
    }

    feed('gk')
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {1:++}{4:89}aba^b{27:ababababababababababababababababababababab}|
      {1:++}{27:a}{4:89}babababababababababababababababababababababab|
      {1:++}abababababababababababababababababababababababab|
      {1:++}ababab                                          |
      {24:-- VISUAL --}                                      |
    ]],
    }

    feed('o')
    screen:expect {
      grid = [[
      ababababababababababababababababababababab{4:01234567}|
      {1:++}{4:89}aba{27:bababababababababababababababababababababab}|
      {1:++}^a{4:89}babababababababababababababababababababababab|
      {1:++}abababababababababababababababababababababababab|
      {1:++}ababab                                          |
      {24:-- VISUAL --}                                      |
    ]],
    }

    feed('<Esc>$')
    command('set number showbreak=')
    screen:expect {
      grid = [[
      {2:  1 }ababababababababababababababababababababab{4:0123}|
      {2:    }{4:456789}abababababababababababababababababababa{4:0}|
      {2:    }{4:123456789}babababababababababababababababababab|
      {2:    }ababababababababababababababababababababababab|
      {2:    }abababababababa^b                              |
                                                        |
    ]],
    }

    command('set cpoptions+=n')
    screen:expect {
      grid = [[
      {2:  1 }ababababababababababababababababababababab{4:0123}|
      {4:456789}abababababababababababababababababababa{4:01234}|
      {4:56789}babababababababababababababababababababababab|
      ababababababababababababababababababababababababab|
      aba^b                                              |
                                                        |
    ]],
    }

    feed('0g$hi<Tab>')
    screen:expect {
      grid = [[
      {2:  1 }ababababababababababababababababababababab{4:01}  |
        {4:^23456789}abababababababababababababababababababa{4:0}|
      {4:123456789}babababababababababababababababababababab|
      ababababababababababababababababababababababababab|
      abababab                                          |
      {24:-- INSERT --}                                      |
    ]],
    }
  end)

  it('virt_text_hide hides overlay virtual text when extmark is off-screen', function()
    screen:try_resize(50, 3)
    command('set nowrap')
    api.nvim_buf_set_lines(0, 0, -1, true, { '-- ' .. ('…'):rep(57) })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { '?????', 'ErrorMsg' } }, virt_text_pos = 'overlay', virt_text_hide = true })
    api.nvim_buf_set_extmark(0, ns, 0, 123, { virt_text = { { '!!!!!', 'ErrorMsg' } }, virt_text_pos = 'overlay', virt_text_hide = true })
    screen:expect {
      grid = [[
      {4:^?????}……………………………………………………………………………………………………{4:!!!!!}……|
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('40zl')
    screen:expect {
      grid = [[
      ^………{4:!!!!!}………………………………                              |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('3zl')
    screen:expect {
      grid = [[
      {4:^!!!!!}………………………………                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('7zl')
    screen:expect {
      grid = [[
      ^…………………………                                        |
      {1:~                                                 }|
                                                        |
    ]],
    }

    command('set wrap smoothscroll')
    screen:expect {
      grid = [[
      {4:?????}……………………………………………………………………………………………………{4:!!!!!}……|
      ^…………………………                                        |
                                                        |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}………………^…                                        |
      {1:~                                                 }|
                                                        |
    ]],
    }
    screen:try_resize(40, 3)
    screen:expect {
      grid = [[
      {1:<<<}{4:!!!!!}……………………………^…                    |
      {1:~                                       }|
                                              |
    ]],
    }
    feed('<C-Y>')
    screen:expect {
      grid = [[
      {4:?????}……………………………………………………………………………………………|
      ………{4:!!!!!}……………………………^…                    |
                                              |
    ]],
    }
  end)

  it('overlay virtual text works on and after a TAB #24022', function()
    screen:try_resize(40, 3)
    api.nvim_buf_set_lines(0, 0, -1, true, { '\t\tline 1' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'AA', 'Search' } }, virt_text_pos = 'overlay', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 0, 1, { virt_text = { { 'BB', 'Search' } }, virt_text_pos = 'overlay', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'CC', 'Search' } }, virt_text_pos = 'overlay', hl_mode = 'combine' })
    screen:expect {
      grid = [[
      {34:AA}     ^ {34:BB}      {34:CC}ne 1                  |
      {1:~                                       }|
                                              |
    ]],
    }
    command('setlocal list listchars=tab:<->')
    screen:expect {
      grid = [[
      {35:^AA}{1:----->}{35:BB}{1:----->}{34:CC}ne 1                  |
      {1:~                                       }|
                                              |
    ]],
    }
  end)

  it('can have virtual text of overlay position and styling', function()
    insert(example_text)
    feed 'gg'

    command 'set ft=lua'
    command 'syntax on'

    screen:expect {
      grid = [[
      {5:^for} _,item {5:in} {6:ipairs}(items) {5:do}                    |
          {5:local} text, hl_id_cell, count {5:=} unpack(item)  |
          {5:if} hl_id_cell {5:~=} {13:nil} {5:then}                     |
              hl_id {5:=} hl_id_cell                        |
          {5:end}                                           |
          {5:for} _ {5:=} {13:1}, (count {5:or} {13:1}) {5:do}                    |
              {5:local} cell {5:=} line[colpos]                 |
              cell.text {5:=} text                          |
              cell.hl_id {5:=} hl_id                        |
              colpos {5:=} colpos{5:+}{13:1}                         |
          {5:end}                                           |
      {5:end}                                               |
      {1:~                                                 }|*2
                                                        |
    ]],
    }

    command 'hi Blendy guibg=Red blend=30'
    command 'hi! Visual guifg=NONE guibg=LightGrey'
    api.nvim_buf_set_extmark(
      0,
      ns,
      1,
      5,
      { virt_text = { { 'blendy text - here', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'blend' }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      2,
      5,
      { virt_text = { { 'combining color', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'combine' }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      3,
      5,
      { virt_text = { { 'replacing color', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'replace' }
    )

    api.nvim_buf_set_extmark(
      0,
      ns,
      4,
      5,
      { virt_text = { { 'blendy text - here', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'blend', virt_text_hide = true }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      5,
      5,
      { virt_text = { { 'combining color', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'combine', virt_text_hide = true }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      6,
      5,
      { virt_text = { { 'replacing color', 'Blendy' } }, virt_text_pos = 'overlay', hl_mode = 'replace', virt_text_hide = true }
    )

    screen:expect {
      grid = [[
      {5:^for} _,item {5:in} {6:ipairs}(items) {5:do}                    |
          {5:l}{8:blen}{7:dy}{10:e}{7:text}{10:h}{7:-}{10:_}{7:here}ell, count {5:=} unpack(item)  |
          {5:i}{12:c}{11:ombining col}{12:or} {13:nil} {5:then}                     |
           {11:replacing color}d_cell                        |
          {5:e}{8:bl}{7:endy}{10: }{7:text}{10: }{7:-}{10: }{7:here}                           |
          {5:f}{12:co}{11:mbi}{12:n}{11:i}{16:n}{11:g color}t {5:or} {13:1}) {5:do}                    |
           {11:replacing color} line[colpos]                 |
              cell.text {5:=} text                          |
              cell.hl_id {5:=} hl_id                        |
              colpos {5:=} colpos{5:+}{13:1}                         |
          {5:end}                                           |
      {5:end}                                               |
      {1:~                                                 }|*2
                                                        |
    ]],
    }

    feed 'V5G'
    screen:expect {
      grid = [[
      {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do}                    |
      {18:    }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count }{17:=}{18: unpack(item)}  |
      {18:    }{17:i}{12:c}{11:ombining col}{12:or}{18: }{23:nil}{18: }{17:then}                     |
      {18:     }{11:replacing color}{18:d_cell}                        |
      {18:    }{5:^e}{17:nd}                                           |
          {5:f}{12:co}{11:mbi}{12:n}{11:i}{16:n}{11:g color}t {5:or} {13:1}) {5:do}                    |
           {11:replacing color} line[colpos]                 |
              cell.text {5:=} text                          |
              cell.hl_id {5:=} hl_id                        |
              colpos {5:=} colpos{5:+}{13:1}                         |
          {5:end}                                           |
      {5:end}                                               |
      {1:~                                                 }|*2
      {24:-- VISUAL LINE --}                                 |
    ]],
    }

    feed 'jj'
    screen:expect {
      grid = [[
      {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do}                    |
      {18:    }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count }{17:=}{18: unpack(item)}  |
      {18:    }{17:i}{12:c}{11:ombining col}{12:or}{18: }{23:nil}{18: }{17:then}                     |
      {18:     }{11:replacing color}{18:d_cell}                        |
      {18:    }{17:end}                                           |
      {18:    }{17:for}{18: _ }{17:=}{18: }{23:1}{18:, (count }{17:or}{18: }{23:1}{18:) }{17:do}                    |
      {18:    }^ {18:   }{17:local}{18: cell }{17:=}{18: line[colpos]}                 |
              cell.text {5:=} text                          |
              cell.hl_id {5:=} hl_id                        |
              colpos {5:=} colpos{5:+}{13:1}                         |
          {5:end}                                           |
      {5:end}                                               |
      {1:~                                                 }|*2
      {24:-- VISUAL LINE --}                                 |
    ]],
    }
  end)

  it('can have virtual text of right_align and fixed win_col position', function()
    insert(example_text)
    feed 'gg'
    api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = { { 'Very', 'ErrorMsg' } }, virt_text_win_col = 31, hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = { { 'VERY', 'ErrorMsg' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 2, 10, { virt_text = { { 'Much', 'ErrorMsg' } }, virt_text_win_col = 31, hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 2, 10, { virt_text = { { 'MUCH', 'ErrorMsg' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 3, 14, { virt_text = { { 'Error', 'ErrorMsg' } }, virt_text_win_col = 31, hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 3, 14, { virt_text = { { 'ERROR', 'ErrorMsg' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 7, 21, { virt_text = { { '-', 'NonText' } }, virt_text_win_col = 4, hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 7, 21, { virt_text = { { '-', 'NonText' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })
    -- empty virt_text should not change anything
    api.nvim_buf_set_extmark(0, ns, 8, 0, { virt_text = { { '' } }, virt_text_win_col = 14, hl_mode = 'blend' })
    api.nvim_buf_set_extmark(0, ns, 8, 0, { virt_text = { { '' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })

    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if hl_id_cell ~= nil then  {4:Much}           {4:MUCH}|
              hl_id = hl_id_cell     {4:Error}         {4:ERROR}|
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*2
                                                        |
    ]],
    }

    feed '3G12|i<cr><esc>'
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if hl_i                    {4:Much}           {4:MUCH}|
      ^d_cell ~= nil then                                |
              hl_id = hl_id_cell     {4:Error}         {4:ERROR}|
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed 'u:<cr>'
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if hl_i^d_cell ~= nil then  {4:Much}           {4:MUCH}|
              hl_id = hl_id_cell     {4:Error}         {4:ERROR}|
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*2
      :                                                 |
    ]],
    }

    feed '8|i<cr><esc>'
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if                                            |
      ^hl_id_cell ~= nil then         {4:Much}           {4:MUCH}|
              hl_id = hl_id_cell     {4:Error}         {4:ERROR}|
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed 'jI-- <esc>..........'
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if                                            |
      hl_id_cell ~= nil then         {4:Much}           {4:MUCH}|
              --^ -- -- -- -- -- -- --{4:Error}- -- hl_i{4:ERROR}|
      l_id_cell                                         |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 4, 50, { virt_text = { { 'EOL', 'NonText' } } })
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if                                            |
      hl_id_cell ~= nil then         {4:Much}           {4:MUCH}|
              --^ -- -- -- -- -- -- --{4:Error}- -- hl_i{4:ERROR}|
      l_id_cell {1:EOL}                                     |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
                                                        |
    ]],
    }

    feed '.'
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
          if                                            |
      hl_id_cell ~= nil then         {4:Much}           {4:MUCH}|
              --^ -- -- -- -- -- -- -- -- -- -- -- hl_id |
      = hl_id_cell {1:EOL}               {4:Error}         {4:ERROR}|
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
          {1:-}   cell.text = text                         {1:-}|
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
                                                        |
    ]],
    }

    command 'set number'
    screen:expect {
      grid = [[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  2 }    local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
      {2:    }m)                                            |
      {2:  3 }    if                                        |
      {2:  4 }hl_id_cell ~= nil then         {4:Much}       {4:MUCH}|
      {2:  5 }        --^ -- -- -- -- -- -- -- -- -- -- -- hl|
      {2:    }_id = hl_id_cell {1:EOL}           {4:Error}     {4:ERROR}|
      {2:  6 }    end                                       |
      {2:  7 }    for _ = 1, (count or 1) do                |
      {2:  8 }        local cell = line[colpos]             |
      {2:  9 }    {1:-}   cell.text = text                     {1:-}|
      {2: 10 }        cell.hl_id = hl_id                    |
      {2: 11 }        colpos = colpos+1                     |
      {2: 12 }    end                                       |
                                                        |
    ]],
    }

    command 'set cpoptions+=n'
    screen:expect {
      grid = [[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  2 }    local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
      m)                                                |
      {2:  3 }    if                                        |
      {2:  4 }hl_id_cell ~= nil then         {4:Much}       {4:MUCH}|
      {2:  5 }        --^ -- -- -- -- -- -- -- -- -- -- -- hl|
      _id = hl_id_cell {1:EOL}           {4:Error}         {4:ERROR}|
      {2:  6 }    end                                       |
      {2:  7 }    for _ = 1, (count or 1) do                |
      {2:  8 }        local cell = line[colpos]             |
      {2:  9 }    {1:-}   cell.text = text                     {1:-}|
      {2: 10 }        cell.hl_id = hl_id                    |
      {2: 11 }        colpos = colpos+1                     |
      {2: 12 }    end                                       |
                                                        |
    ]],
    }

    command 'set cpoptions-=n nowrap'
    screen:expect {
      grid = [[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  2 }    local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
      {2:  3 }    if                                        |
      {2:  4 }hl_id_cell ~= nil then         {4:Much}       {4:MUCH}|
      {2:  5 }        --^ -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}|
      {2:  6 }    end                                       |
      {2:  7 }    for _ = 1, (count or 1) do                |
      {2:  8 }        local cell = line[colpos]             |
      {2:  9 }    {1:-}   cell.text = text                     {1:-}|
      {2: 10 }        cell.hl_id = hl_id                    |
      {2: 11 }        colpos = colpos+1                     |
      {2: 12 }    end                                       |
      {2: 13 }end                                           |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed '12zl'
    screen:expect {
      grid = [[
      {2:  1 }n ipairs(items) do                            |
      {2:  2 }xt, hl_id_cell, count = unpack({4:Very})      {4:VERY}|
      {2:  3 }                                              |
      {2:  4 }= nil then                     {4:Much}       {4:MUCH}|
      {2:  5 }^- -- -- -- -- -- -- -- -- -- --{4:Error}d = h{4:ERROR}|
      {2:  6 }                                              |
      {2:  7 }1, (count or 1) do                            |
      {2:  8 }l cell = line[colpos]                         |
      {2:  9 }.tex{1:-} = text                                 {1:-}|
      {2: 10 }.hl_id = hl_id                                |
      {2: 11 }os = colpos+1                                 |
      {2: 12 }                                              |
      {2: 13 }                                              |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('fhi<Tab>')
    screen:expect {
      grid = [[
      {2:  1 }n ipairs(items) do                            |
      {2:  2 }xt, hl_id_cell, count = unpack({4:Very})      {4:VERY}|
      {2:  3 }                                              |
      {2:  4 }= nil then                     {4:Much}       {4:MUCH}|
      {2:  5 }- -- -- -- -- -- -- -- -- -- --{4:Error}^hl_id{4:ERROR}|
      {2:  6 }                                              |
      {2:  7 }1, (count or 1) do                            |
      {2:  8 }l cell = line[colpos]                         |
      {2:  9 }.tex{1:-} = text                                 {1:-}|
      {2: 10 }.hl_id = hl_id                                |
      {2: 11 }os = colpos+1                                 |
      {2: 12 }                                              |
      {2: 13 }                                              |
      {1:~                                                 }|
      {24:-- INSERT --}                                      |
    ]],
    }

    feed('<Esc>0')
    screen:expect {
      grid = [[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  2 }    local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
      {2:  3 }    if                                        |
      {2:  4 }hl_id_cell ~= nil then         {4:Much}       {4:MUCH}|
      {2:  5 }^        -- -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}|
      {2:  6 }    end                                       |
      {2:  7 }    for _ = 1, (count or 1) do                |
      {2:  8 }        local cell = line[colpos]             |
      {2:  9 }    {1:-}   cell.text = text                     {1:-}|
      {2: 10 }        cell.hl_id = hl_id                    |
      {2: 11 }        colpos = colpos+1                     |
      {2: 12 }    end                                       |
      {2: 13 }end                                           |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('virtual text win_col out of window does not break display #25645', function()
    screen:try_resize(51, 6)
    command('vnew')
    api.nvim_buf_set_lines(0, 0, -1, false, { string.rep('a', 50) })
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaa│                         |
      aaaaaaaaaaaaaaaaaaaaaaaaa│{1:~                        }|
      {1:~                        }│{1:~                        }|*2
      {41:[No Name] [+]             }{40:[No Name]                }|
                                                         |
    ]],
    }
    local extmark_opts = { virt_text_win_col = 35, virt_text = { { ' ', 'Comment' } } }
    api.nvim_buf_set_extmark(0, ns, 0, 0, extmark_opts)
    screen:expect_unchanged()
    assert_alive()
  end)

  it('can have virtual text on folded line', function()
    insert([[
      11111
      22222
      33333]])
    command('1,2fold')
    screen:try_resize(50, 3)
    feed('zb')
    -- XXX: the behavior of overlay virtual text at non-zero column is strange:
    -- 1. With 'wrap' it is never shown.
    -- 2. With 'nowrap' it is shown only if the extmark is hidden before leftcol.
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'AA', 'Underlined' } }, hl_mode = 'combine', virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 0, 5, { virt_text = { { 'BB', 'Underlined' } }, hl_mode = 'combine', virt_text_win_col = 10 })
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'CC', 'Underlined' } }, hl_mode = 'combine', virt_text_pos = 'right_align' })
    screen:expect {
      grid = [[
      {29:AA}{33:-  2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
      3333^3                                             |
                                                        |
    ]],
    }
    command('set nowrap')
    screen:expect_unchanged()
    feed('zl')
    screen:expect {
      grid = [[
      {29:AA}{33:-  2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
      333^3                                              |
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {29:AA}{33:-  2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
      33^3                                               |
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {29:AA}{33:-  2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
      3^3                                                |
                                                        |
    ]],
    }
  end)

  it('virtual text works below diff filler lines', function()
    screen:try_resize(53, 8)
    insert([[
      aaaaa
      bbbbb
      ccccc
      ddddd
      eeeee]])
    command('rightbelow vnew')
    insert([[
      bbbbb
      ccccc
      ddddd
      eeeee]])
    command('windo diffthis')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'AA', 'Underlined' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'BB', 'Underlined' } }, virt_text_win_col = 10 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'CC', 'Underlined' } }, virt_text_pos = 'right_align' })
    screen:expect {
      grid = [[
      {37:  }{38:aaaaa                   }│{37:  }{39:------------------------}|
      {37:  }bbbbb                   │{37:  }{28:AA}bbb     {28:BB}          {28:CC}|
      {37:  }ccccc                   │{37:  }ccccc                   |
      {37:  }ddddd                   │{37:  }ddddd                   |
      {37:  }eeeee                   │{37:  }eeee^e                   |
      {1:~                         }│{1:~                         }|
      {40:[No Name] [+]              }{41:[No Name] [+]             }|
                                                           |
    ]],
    }
    command('windo set wrap')
    screen:expect_unchanged()
  end)

  it('can have virtual text which combines foreground and background groups', function()
    screen:try_resize(20, 5)

    screen:set_default_attr_ids {
      [1] = { bold = true, foreground = Screen.colors.Blue },
      [2] = { background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb') },
      [3] = { background = tonumber('0x123456'), foreground = tonumber('0xcccccc') },
      [4] = { background = tonumber('0x234567'), foreground = tonumber('0xbbbbbb') },
      [5] = { background = tonumber('0x234567'), foreground = tonumber('0xcccccc') },
      [6] = { bold = true, foreground = tonumber('0xcccccc'), background = tonumber('0x234567') },
    }

    exec [[
      hi BgOne guibg=#123456
      hi BgTwo guibg=#234567
      hi FgEin guifg=#bbbbbb
      hi FgZwei guifg=#cccccc
      hi VeryBold gui=bold
    ]]

    insert('##')
    local vt = {
      { 'a', { 'BgOne', 'FgEin' } },
      { 'b', { 'BgOne', 'FgZwei' } },
      { 'c', { 'BgTwo', 'FgEin' } },
      { 'd', { 'BgTwo', 'FgZwei' } },
      { 'X', { 'BgTwo', 'FgZwei', 'VeryBold' } },
    }
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'eol' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'right_align' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { vt, vt } })
    screen:expect {
      grid = [[
      {2:a}{3:b}{4:c}{5:d}{6:X}#^# {2:a}{3:b}{4:c}{5:d}{6:X}  {2:a}{3:b}{4:c}{5:d}{6:X}|
      {2:a}{3:b}{4:c}{5:d}{6:X}               |*2
      {1:~                   }|
                          |
    ]],
    }
  end)

  it('does not crash when deleting a cleared buffer #15212', function()
    exec_lua [[
      ns = vim.api.nvim_create_namespace("myplugin")
      vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {virt_text = {{"a"}}, end_col = 0})
    ]]
    screen:expect {
      grid = [[
      ^ a                                                |
      {1:~                                                 }|*13
                                                        |
    ]],
    }

    exec_lua [[
      vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
      vim.cmd("bdelete")
    ]]
    screen:expect {
      grid = [[
      ^                                                  |
      {1:~                                                 }|*13
                                                        |
    ]],
    }
    assert_alive()
  end)

  it('conceal with conceal char #19007', function()
    screen:try_resize(50, 5)
    insert('foo\n')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 0, end_row = 2, conceal = 'X' })
    command('set conceallevel=2')
    screen:expect([[
      {26:X}                                                 |
      ^                                                  |
      {1:~                                                 }|*2
                                                        |
    ]])
    command('set conceallevel=1')
    screen:expect_unchanged()

    eq('conceal char has to be printable', pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_col = 0, end_row = 2, conceal = '\255' }))
  end)

  it('conceal with composed conceal char', function()
    screen:try_resize(50, 5)
    insert('foo\n')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 0, end_row = 2, conceal = 'ẍ̲' })
    command('set conceallevel=2')
    screen:expect([[
      {26:ẍ̲}                                                 |
      ^                                                  |
      {1:~                                                 }|*2
                                                        |
    ]])
    command('set conceallevel=1')
    screen:expect_unchanged()

    -- this is rare, but could happen. Save at least the first codepoint
    api.nvim__invalidate_glyph_cache()
    screen:expect {
      grid = [[
      {26:x}                                                 |
      ^                                                  |
      {1:~                                                 }|*2
                                                        |
    ]],
    }
  end)

  it('conceal without conceal char #24782', function()
    screen:try_resize(50, 5)
    insert('foobar\n')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 3, conceal = '' })
    command('set listchars=conceal:?')
    command('let &conceallevel=1')
    screen:expect([[
      {26:?}bar                                              |
      ^                                                  |
      {1:~                                                 }|*2
                                                        |
    ]])
    command('let &conceallevel=2')
    screen:expect([[
      bar                                               |
      ^                                                  |
      {1:~                                                 }|*2
                                                        |
    ]])
  end)

  it('conceal works just before truncated double-width char #21486', function()
    screen:try_resize(40, 4)
    api.nvim_buf_set_lines(0, 0, -1, true, { '', ('a'):rep(37) .. '<>古' })
    api.nvim_buf_set_extmark(0, ns, 1, 37, { end_col = 39, conceal = '' })
    command('setlocal conceallevel=2')
    screen:expect {
      grid = [[
      ^                                        |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:>}  |
      古                                      |
                                              |
    ]],
    }
    feed('j')
    screen:expect {
      grid = [[
                                              |
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<>{1:>}|
      古                                      |
                                              |
    ]],
    }
  end)

  it('redraws properly when adding/removing conceal on non-current line', function()
    screen:try_resize(50, 5)
    api.nvim_buf_set_lines(0, 0, -1, true, { 'abcd', 'efgh', 'ijkl', 'mnop' })
    command('setlocal conceallevel=2')
    screen:expect {
      grid = [[
      ^abcd                                              |
      efgh                                              |
      ijkl                                              |
      mnop                                              |
                                                        |
    ]],
    }
    api.nvim_buf_set_extmark(0, ns, 2, 1, { end_col = 3, conceal = '' })
    screen:expect {
      grid = [[
      ^abcd                                              |
      efgh                                              |
      il                                                |
      mnop                                              |
                                                        |
    ]],
    }
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    screen:expect {
      grid = [[
      ^abcd                                              |
      efgh                                              |
      ijkl                                              |
      mnop                                              |
                                                        |
    ]],
    }
  end)

  it('avoids redraw issue #20651', function()
    exec_lua [[
      vim.cmd.normal'10oXXX'
      vim.cmd.normal'gg'
      local ns = vim.api.nvim_create_namespace('ns')

      local bufnr = vim.api.nvim_create_buf(false, true)
      vim.api.nvim_open_win(bufnr, false, { relative = 'win', height = 1, width = 1, row = 0, col = 0 })

      vim.api.nvim_create_autocmd('CursorMoved', { callback = function()
        local row = vim.api.nvim_win_get_cursor(0)[1] - 1
        vim.api.nvim_buf_set_extmark(0, ns, row, 0, { id = 1 })
        vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
        vim.schedule(function()
          vim.api.nvim_buf_set_extmark(0, ns, row, 0, {
            id = 1,
            virt_text = {{'HELLO', 'Normal'}},
          })
        end)
      end
      })
    ]]

    for _ = 1, 3 do
      vim.uv.sleep(10)
      feed 'j'
    end

    screen:expect {
      grid = [[
      {44: }                                                 |
      XXX                                               |*2
      ^XXX HELLO                                         |
      XXX                                               |*7
      {1:~                                                 }|*3
                                                        |
    ]],
    }
  end)

  it('underline attribute with higher priority takes effect #22371', function()
    screen:try_resize(50, 3)
    insert('aaabbbaaa')
    exec([[
      hi TestUL gui=underline guifg=Blue
      hi TestUC gui=undercurl guisp=Red
      hi TestBold gui=bold
    ]])
    screen:set_default_attr_ids({
      [0] = { bold = true, foreground = Screen.colors.Blue },
      [1] = { underline = true, foreground = Screen.colors.Blue },
      [2] = { undercurl = true, special = Screen.colors.Red },
      [3] = { underline = true, foreground = Screen.colors.Blue, special = Screen.colors.Red },
      [4] = { undercurl = true, foreground = Screen.colors.Blue, special = Screen.colors.Red },
      [5] = { bold = true, underline = true, foreground = Screen.colors.Blue },
      [6] = { bold = true, undercurl = true, special = Screen.colors.Red },
    })

    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 20 })
    api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUC', priority = 30 })
    screen:expect([[
      {1:aaa}{4:bbb}{1:aa^a}                                         |
      {0:~                                                 }|
                                                        |
    ]])
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 20 })
    api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUL', priority = 30 })
    screen:expect([[
      {2:aaa}{3:bbb}{2:aa^a}                                         |
      {0:~                                                 }|
                                                        |
    ]])
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 30 })
    api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUC', priority = 20 })
    screen:expect([[
      {1:aaa}{3:bbb}{1:aa^a}                                         |
      {0:~                                                 }|
                                                        |
    ]])
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 30 })
    api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUL', priority = 20 })
    screen:expect([[
      {2:aaa}{4:bbb}{2:aa^a}                                         |
      {0:~                                                 }|
                                                        |
    ]])

    -- When only one highlight group has an underline attribute, it should always take effect.
    for _, d in ipairs({ -5, 5 }) do
      api.nvim_buf_clear_namespace(0, ns, 0, -1)
      screen:expect([[
        aaabbbaa^a                                         |
        {0:~                                                 }|
                                                          |
      ]])
      api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 25 + d })
      api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestBold', priority = 25 - d })
      screen:expect([[
        {1:aaa}{5:bbb}{1:aa^a}                                         |
        {0:~                                                 }|
                                                          |
      ]])
    end
    for _, d in ipairs({ -5, 5 }) do
      api.nvim_buf_clear_namespace(0, ns, 0, -1)
      screen:expect([[
        aaabbbaa^a                                         |
        {0:~                                                 }|
                                                          |
      ]])
      api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 25 + d })
      api.nvim_buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestBold', priority = 25 - d })
      screen:expect([[
        {2:aaa}{6:bbb}{2:aa^a}                                         |
        {0:~                                                 }|
                                                          |
      ]])
    end
  end)

  it('highlight is combined with syntax and sign linehl #20004', function()
    screen:try_resize(50, 3)
    insert([[
      function Func()
      end]])
    feed('gg')
    command('set ft=lua')
    command('syntax on')
    command('hi default MyMark guibg=LightGrey')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 3, hl_mode = 'combine', hl_group = 'MyMark' })
    command('hi default MyLine gui=underline')
    command('sign define CurrentLine linehl=MyLine')
    fn.sign_place(6, 'Test', 'CurrentLine', '', { lnum = 1 })
    screen:expect {
      grid = [[
      {30:^fun}{31:ction}{32: Func()                                   }|
      {6:end}                                               |
                                                        |
    ]],
    }
  end)

  it('highlight can combine multiple groups', function()
    screen:try_resize(50, 3)
    command('hi Group1 guibg=Red guifg=Red guisp=Red')
    command('hi Group2 guibg=Blue guifg=Blue')
    command('hi Group3 guibg=Green')
    insert([[example text]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {} })
    screen:expect([[
      example tex^t                                      |
      {1:~                                                 }|
                                                        |
    ]])

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = { 'Group1' } })
    screen:expect([[
      {45:example tex^t}                                      |
      {1:~                                                 }|
                                                        |
    ]])
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = { 'Group1', 'Group2' } })
    screen:expect([[
      {46:example tex^t}                                      |
      {1:~                                                 }|
                                                        |
    ]])
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = { 'Group1', 'Group2', 'Group3' }, hl_eol = true })
    screen:expect([[
      {47:example tex^t                                      }|
      {1:~                                                 }|
                                                        |
    ]])

    eq(
      "Invalid 'hl_group': 'hl_group item'",
      pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = { 'Group1', 'Group2', { 'fail' } }, hl_eol = true })
    )
  end)

  it('highlight works after TAB with sidescroll #14201', function()
    screen:try_resize(50, 3)
    command('set nowrap')
    api.nvim_buf_set_lines(0, 0, -1, true, { '\tword word word word' })
    api.nvim_buf_set_extmark(0, ns, 0, 1, { end_col = 3, hl_group = 'ErrorMsg' })
    screen:expect {
      grid = [[
             ^ {4:wo}rd word word word                       |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('7zl')
    screen:expect {
      grid = [[
       {4:^wo}rd word word word                              |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {4:^wo}rd word word word                               |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {4:^o}rd word word word                                |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('highlights the beginning of a TAB char correctly #23734', function()
    screen:try_resize(50, 3)
    api.nvim_buf_set_lines(0, 0, -1, true, { 'this is the\ttab' })
    api.nvim_buf_set_extmark(0, ns, 0, 11, { end_col = 15, hl_group = 'ErrorMsg' })
    screen:expect {
      grid = [[
      ^this is the{4:     tab}                               |
      {1:~                                                 }|
                                                        |
    ]],
    }

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 0, 12, { end_col = 15, hl_group = 'ErrorMsg' })
    screen:expect {
      grid = [[
      ^this is the     {4:tab}                               |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('highlight applies to a full TAB on line with matches #20885', function()
    screen:try_resize(50, 3)
    api.nvim_buf_set_lines(0, 0, -1, true, { '\t-- match1', '        -- match2' })
    fn.matchadd('NonText', 'match')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, end_col = 0, hl_group = 'Search' })
    api.nvim_buf_set_extmark(0, ns, 1, 0, { end_row = 2, end_col = 0, hl_group = 'Search' })
    screen:expect {
      grid = [[
      {34:       ^ -- }{35:match}{34:1}                                 |
      {34:        -- }{35:match}{34:2}                                 |
                                                        |
    ]],
    }
  end)

  it('highlight applies to a full TAB in visual block mode', function()
    screen:try_resize(50, 8)
    command('hi! Visual guifg=NONE guibg=LightGrey')
    api.nvim_buf_set_lines(0, 0, -1, true, { 'asdf', '\tasdf', '\tasdf', '\tasdf', 'asdf' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 5, end_col = 0, hl_group = 'Underlined' })
    screen:expect([[
      {28:^asdf}                                              |
      {28:        asdf}                                      |*3
      {28:asdf}                                              |
      {1:~                                                 }|*2
                                                        |
    ]])
    feed('<C-V>Gll')
    screen:expect([[
      {29:asd}{28:f}                                              |
      {29:   }{28:     asdf}                                      |*3
      {29:as}{28:^df}                                              |
      {1:~                                                 }|*2
      {24:-- VISUAL BLOCK --}                                |
    ]])
  end)

  it('highlight works properly with multibyte text and spell #26771', function()
    insert('口口\n')
    screen:try_resize(50, 3)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 3, hl_group = 'Search' })
    screen:expect([[
      {34:口}口                                              |
      ^                                                  |
                                                        |
    ]])
    command('setlocal spell')
    screen:expect([[
      {43:口}{42:口}                                              |
      ^                                                  |
                                                        |
    ]])
  end)

  it('supports multiline highlights', function()
    insert(example_text)
    feed 'gg'
    for _, i in ipairs { 1, 2, 3, 5, 6, 7 } do
      for _, j in ipairs { 2, 5, 10, 15 } do
        api.nvim_buf_set_extmark(0, ns, i, j, { end_col = j + 2, hl_group = 'NonText' })
      end
    end
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
        {1:  }l{1:oc}al {1:te}xt,{1: h}l_id_cell, count = unpack(item)  |
        {1:  }i{1:f }hl_{1:id}_ce{1:ll} ~= nil then                     |
        {1:  } {1:  } hl{1:_i}d ={1: h}l_id_cell                        |
          end                                           |
        {1:  }f{1:or} _ {1:= }1, {1:(c}ount or 1) do                    |
        {1:  } {1:  } lo{1:ca}l c{1:el}l = line[colpos]                 |
        {1:  } {1:  } ce{1:ll}.te{1:xt} = text                          |
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*2
                                                        |
    ]],
    }
    feed '5<c-e>'
    screen:expect {
      grid = [[
      ^  {1:  }f{1:or} _ {1:= }1, {1:(c}ount or 1) do                    |
        {1:  } {1:  } lo{1:ca}l c{1:el}l = line[colpos]                 |
        {1:  } {1:  } ce{1:ll}.te{1:xt} = text                          |
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*7
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 1, 0, { end_line = 8, end_col = 10, hl_group = 'ErrorMsg' })
    screen:expect {
      grid = [[
      {4:^  }{36:  }{4:f}{36:or}{4: _ }{36:= }{4:1, }{36:(c}{4:ount or 1) do}                    |
      {4:  }{36:  }{4: }{36:  }{4: lo}{36:ca}{4:l c}{36:el}{4:l = line[colpos]}                 |
      {4:  }{36:  }{4: }{36:  }{4: ce}{36:ll}{4:.te}{36:xt}{4: = text}                          |
      {4:        ce}ll.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      end                                               |
      {1:~                                                 }|*7
                                                        |
    ]],
    }
  end)

  local function with_undo_restore(val)
    screen:try_resize(50, 5)
    insert(example_text)
    feed 'gg'
    api.nvim_buf_set_extmark(0, ns, 0, 6, { end_col = 13, hl_group = 'NonText', undo_restore = val })
    screen:expect {
      grid = [[
      ^for _,{1:item in} ipairs(items) do                    |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
                                                        |
    ]],
    }

    api.nvim_buf_set_text(0, 0, 4, 0, 8, { '' })
    screen:expect {
      grid = [[
      ^for {1:em in} ipairs(items) do                        |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
                                                        |
    ]],
    }
  end

  it('highlights do reapply to restored text after delete', function()
    with_undo_restore(true) -- also default behavior

    command('silent undo')
    screen:expect {
      grid = [[
      ^for _,{1:item in} ipairs(items) do                    |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
                                                        |
    ]],
    }
  end)

  it("highlights don't reapply to restored text after delete with undo_restore=false", function()
    with_undo_restore(false)

    command('silent undo')
    screen:expect {
      grid = [[
      ^for _,it{1:em in} ipairs(items) do                    |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
                                                        |
    ]],
    }

    eq({
      {
        1,
        0,
        8,
        {
          end_col = 13,
          end_right_gravity = false,
          end_row = 0,
          hl_eol = false,
          hl_group = 'NonText',
          undo_restore = false,
          ns_id = ns,
          priority = 4096,
          right_gravity = true,
        },
      },
    }, api.nvim_buf_get_extmarks(0, ns, { 0, 0 }, { 0, -1 }, { details = true }))
  end)

  it('virtual text works with rightleft', function()
    screen:try_resize(50, 3)
    insert('abcdefghijklmn')
    feed('0')
    command('set rightleft')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'EOL', 'Underlined' } } })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'right_align', 'Underlined' } }, virt_text_pos = 'right_align' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'win_col', 'Underlined' } }, virt_text_win_col = 20 })
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'overlayed', 'Underlined' } }, virt_text_pos = 'overlay' })
    screen:expect {
      grid = [[
      {28:ngila_thgir}            {28:loc_niw}  {28:LOE} nml{28:deyalrevo}b^a|
      {1:                                                 ~}|
                                                        |
    ]],
    }

    insert(('#'):rep(32))
    feed('0')
    screen:expect {
      grid = [[
      {28:ngila_tdeyalrevo}ba#####{28:loc_niw}###################^#|
      {1:                                                 ~}|
                                                        |
    ]],
    }

    insert(('#'):rep(16))
    feed('0')
    screen:expect {
      grid = [[
      {28:ngila_thgir}############{28:loc_niw}###################^#|
                                        {28:LOE} nml{28:deyalrevo}|
                                                        |
    ]],
    }

    insert('###')
    feed('0')
    screen:expect {
      grid = [[
      #################################################^#|
      {28:ngila_thgir}            {28:loc_niw} {28:LOE} nml{28:deyalrevo}ba#|
                                                        |
    ]],
    }

    command('set number')
    screen:expect {
      grid = [[
      #############################################^#{2: 1  }|
      {28:ngila_thgir}        {28:loc_niw} nml{28:deyalrevo}ba#####{2:    }|
                                                        |
    ]],
    }

    command('set cpoptions+=n')
    screen:expect {
      grid = [[
      #############################################^#{2: 1  }|
      {28:ngila_thgir}            {28:loc_niw} nml{28:deyalrevo}ba#####|
                                                        |
    ]],
    }
  end)

  it('virtual text overwrites double-width char properly', function()
    screen:try_resize(50, 3)
    insert('abcdefghij口klmnopqrstu口vwx口yz')
    feed('0')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { '!!!!!', 'Underlined' } }, virt_text_win_col = 11 })
    screen:expect {
      grid = [[
      ^abcdefghij {28:!!!!!}opqrstu口vwx口yz                  |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('8x')
    screen:expect {
      grid = [[
      ^ij口klmnopq{28:!!!!!} vwx口yz                          |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('3l5x')
    screen:expect {
      grid = [[
      ij口^pqrstu {28:!!!!!} yz                               |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('5x')
    screen:expect {
      grid = [[
      ij口^u口vwx {28:!!!!!}                                  |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('virtual text blending space does not overwrite double-width char', function()
    screen:try_resize(50, 3)
    insert('abcdefghij口klmnopqrstu口vwx口yz')
    feed('0')
    command('hi Blendy guibg=Red blend=30')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { ' !  ! ', 'Blendy' } }, virt_text_win_col = 8, hl_mode = 'blend' })
    screen:expect {
      grid = [[
      ^abcdefgh{10:i}{7:!}{10:口}{7:!}{10:l}mnopqrstu口vwx口yz                  |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('x')
    screen:expect {
      grid = [[
      ^bcdefghi{10:j}{7:!}{10: k}{7:!}{10:m}nopqrstu口vwx口yz                   |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('x')
    screen:expect {
      grid = [[
      ^cdefghij{10: }{7:!}{10:kl}{7:!}{10:n}opqrstu口vwx口yz                    |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('x')
    screen:expect {
      grid = [[
      ^defghij口{7:!}{10:lm}{7:!}{10:o}pqrstu口vwx口yz                     |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('7x')
    screen:expect {
      grid = [[
      ^口klmnop{10:q}{7:!}{10:st}{7:!}{10:口}vwx口yz                            |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('virtual text works with double-width char and rightleft', function()
    screen:try_resize(50, 3)
    insert('abcdefghij口klmnopqrstu口vwx口yz')
    feed('0')
    command('set rightleft')
    screen:expect {
      grid = [[
                        zy口xwv口utsrqponmlk口jihgfedcb^a|
      {1:                                                 ~}|
                                                        |
    ]],
    }
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'overlayed', 'Underlined' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 0, 14, { virt_text = { { '古', 'Underlined' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 0, 20, { virt_text = { { '\t', 'Underlined' } }, virt_text_pos = 'overlay' })
    api.nvim_buf_set_extmark(0, ns, 0, 29, { virt_text = { { '古', 'Underlined' } }, virt_text_pos = 'overlay' })
    screen:expect {
      grid = [[
                        zy {28:古}wv {28:     }qpon{28:古}k {28:deyalrevo}b^a|
      {1:                                                 ~}|
                                                        |
    ]],
    }
  end)

  it('virtual text is drawn correctly after delete and undo #27368', function()
    insert('aaa\nbbb\nccc\nddd\neee')
    command('vsplit')
    api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = { { 'EOL' } } })
    feed('3gg')
    screen:expect {
      grid = [[
      aaa                      │aaa                     |
      bbb                      │bbb                     |
      ^ccc EOL                  │ccc EOL                 |
      ddd                      │ddd                     |
      eee                      │eee                     |
      {1:~                        }│{1:~                       }|*8
      {41:[No Name] [+]             }{40:[No Name] [+]           }|
                                                        |
    ]],
    }
    feed('dd')
    screen:expect {
      grid = [[
      aaa                      │aaa                     |
      bbb                      │bbb                     |
      ^ddd EOL                  │ddd EOL                 |
      eee                      │eee                     |
      {1:~                        }│{1:~                       }|*9
      {41:[No Name] [+]             }{40:[No Name] [+]           }|
                                                        |
    ]],
    }
    command('silent undo')
    screen:expect {
      grid = [[
      aaa                      │aaa                     |
      bbb                      │bbb                     |
      ^ccc EOL                  │ccc EOL                 |
      ddd                      │ddd                     |
      eee                      │eee                     |
      {1:~                        }│{1:~                       }|*8
      {41:[No Name] [+]             }{40:[No Name] [+]           }|
                                                        |
    ]],
    }
  end)

  it('virtual text does not crash with blend, conceal and wrap #27836', function()
    screen:try_resize(50, 3)
    insert(('a'):rep(45) .. '|hidden|' .. ('b'):rep(45))
    command('syntax match test /|hidden|/ conceal')
    command('set conceallevel=2 concealcursor=n')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'FOO' } }, virt_text_pos = 'right_align', hl_mode = 'blend' })
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  FOO|
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b     |
                                                        |
    ]],
    }
  end)

  it('works with both hl_group and sign_hl_group', function()
    screen:try_resize(50, 3)
    screen:add_extra_attr_ids({
      [100] = { background = Screen.colors.WebGray, foreground = Screen.colors.Blue, bold = true },
    })
    insert('abcdefghijklmn')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S', sign_hl_group = 'NonText', hl_group = 'Error', end_col = 14 })
    screen:expect([[
      {100:S }{9:abcdefghijklm^n}                                  |
      {1:~                                                 }|
                                                        |
    ]])
  end)

  it('virt_text_repeat_linebreak repeats virtual text on wrapped lines', function()
    screen:try_resize(40, 5)
    api.nvim_set_option_value('breakindent', true, {})
    insert(example_text)
    api.nvim_buf_set_extmark(
      0,
      ns,
      1,
      0,
      { virt_text = { { '│', 'NonText' } }, virt_text_pos = 'overlay', virt_text_repeat_linebreak = true }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      1,
      3,
      { virt_text = { { '│', 'NonText' } }, virt_text_pos = 'overlay', virt_text_repeat_linebreak = true }
    )
    command('norm gg')
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do          |
      {1:│}  {1:│}local text, hl_id_cell, count = unpa|
      {1:│}  {1:│}ck(item)                            |
          if hl_id_cell ~= nil then           |
                                              |
    ]],
    }
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(
      0,
      ns,
      1,
      0,
      { virt_text = { { '│', 'NonText' } }, virt_text_repeat_linebreak = true, virt_text_win_col = 0 }
    )
    api.nvim_buf_set_extmark(
      0,
      ns,
      1,
      0,
      { virt_text = { { '│', 'NonText' } }, virt_text_repeat_linebreak = true, virt_text_win_col = 2 }
    )
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do          |
      {1:│} {1:│} local text, hl_id_cell, count = unpa|
      {1:│} {1:│} ck(item)                            |
          if hl_id_cell ~= nil then           |
                                              |
    ]],
    }
  end)

  it('supports URLs', function()
    insert(example_text)

    local url1 = 'https://example.com'
    local url2 = 'http://127.0.0.1'

    screen:add_extra_attr_ids {
      u = { url = url1 },
      uh = { url = url2, background = Screen.colors.Yellow },
    }

    api.nvim_buf_set_extmark(0, ns, 1, 4, {
      end_col = 14,
      url = url1,
    })
    api.nvim_buf_set_extmark(0, ns, 2, 4, {
      end_col = 17,
      hl_group = 'Search',
      url = url2,
    })

    screen:expect([[
      for _,item in ipairs(items) do                    |
          {u:local text}, hl_id_cell, count = unpack(item)  |
          {uh:if hl_id_cell} ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
              cell.hl_id = hl_id                        |
              colpos = colpos+1                         |
          end                                           |
      en^d                                               |
      {1:~                                                 }|*2
                                                        |
    ]])
  end)

  it('can replace marks in place with different decorations #27211', function()
    local mark = api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'foo', 'ErrorMsg' } } } })
    screen:expect {
      grid = [[
      ^                                                  |
      {4:foo}                                               |
      {1:~                                                 }|*12
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 0, 0, {
      id = mark,
      virt_text = { { 'testing', 'NonText' } },
      virt_text_pos = 'inline',
    })
    screen:expect {
      grid = [[
      {1:^testing}                                           |
      {1:~                                                 }|*13
                                                        |
    ]],
    }

    api.nvim_buf_del_extmark(0, ns, mark)
    screen:expect {
      grid = [[
      ^                                                  |
      {1:~                                                 }|*13
                                                        |
    ]],
    }

    n.assert_alive()
  end)

  it('priority ordering of overlay or win_col virtual text at same position', function()
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'A' } }, virt_text_pos = 'overlay', priority = 100 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'A' } }, virt_text_win_col = 30, priority = 100 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'BB' } }, virt_text_pos = 'overlay', priority = 90 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'BB' } }, virt_text_win_col = 30, priority = 90 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'CCC' } }, virt_text_pos = 'overlay', priority = 80 })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'CCC' } }, virt_text_win_col = 30, priority = 80 })
    screen:expect([[
      ^ABC                           ABC                 |
      {1:~                                                 }|*13
                                                        |
    ]])
  end)

  it('priority ordering of inline and non-inline virtual text at same char', function()
    insert(('?'):rep(40) .. ('!'):rep(30))
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'A' } }, virt_text_pos = 'overlay', priority = 10 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'a' } }, virt_text_win_col = 15, priority = 10 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'BBBB' } }, virt_text_pos = 'inline', priority = 15 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'C' } }, virt_text_pos = 'overlay', priority = 20 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'c' } }, virt_text_win_col = 17, priority = 20 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'DDDD' } }, virt_text_pos = 'inline', priority = 25 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'E' } }, virt_text_pos = 'overlay', priority = 30 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'e' } }, virt_text_win_col = 19, priority = 30 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'FFFF' } }, virt_text_pos = 'inline', priority = 35 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'G' } }, virt_text_pos = 'overlay', priority = 40 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'g' } }, virt_text_win_col = 21, priority = 40 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'HHHH' } }, virt_text_pos = 'inline', priority = 45 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'I' } }, virt_text_pos = 'overlay', priority = 50 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'i' } }, virt_text_win_col = 23, priority = 50 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'JJJJ' } }, virt_text_pos = 'inline', priority = 55 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'K' } }, virt_text_pos = 'overlay', priority = 60 })
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { 'k' } }, virt_text_win_col = 25, priority = 60 })
    screen:expect([[
      ???????????????a?c?e????????????????????ABBBCDDDEF|
      FFGHHHIJJJK!!!!!!!!!!g!i!k!!!!!!!!!!!!!^!          |
      {1:~                                                 }|*12
                                                        |
    ]])
    feed('02x$')
    screen:expect([[
      ???????????????a?c?e??????????????????ABBBCDDDEFFF|
      GHHHIJJJK!!!!!!!!!!!!g!i!k!!!!!!!!!!!^!            |
      {1:~                                                 }|*12
                                                        |
    ]])
    feed('02x$')
    screen:expect([[
      ???????????????a?c?e?g??????????????ABBBCDDDEFFFGH|
      HHIJJJK!!!!!!!!!!!!!!!!i!k!!!!!!!!!^!              |
      {1:~                                                 }|*12
                                                        |
    ]])
    feed('02x$')
    screen:expect([[
      ???????????????a?c?e?g????????????ABBBCDDDEFFFGHHH|
      IJJJK!!!!!!!!!!!!!!!!!!i!k!!!!!!!^!                |
      {1:~                                                 }|*12
                                                        |
    ]])
    command('set nowrap')
    feed('0')
    screen:expect([[
      ^???????????????a?c?e?g?i?k????????ABBBCDDDEFFFGHHH|
      {1:~                                                 }|*13
                                                        |
    ]])
    feed('2x')
    screen:expect([[
      ^???????????????a?c?e?g?i?k??????ABBBCDDDEFFFGHHHIJ|
      {1:~                                                 }|*13
                                                        |
    ]])
    feed('2x')
    screen:expect([[
      ^???????????????a?c?e?g?i?k????ABBBCDDDEFFFGHHHIJJJ|
      {1:~                                                 }|*13
                                                        |
    ]])
    feed('2x')
    screen:expect([[
      ^???????????????a?c?e?g?i?k??ABBBCDDDEFFFGHHHIJJJK!|
      {1:~                                                 }|*13
                                                        |
    ]])
  end)

  it('conceal_lines', function()
    insert(example_text)
    exec('set number conceallevel=3')
    feed('ggj')
    local not_concealed = {
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  2 }^    local text, hl_id_cell, count = unpack(ite|
        {2:    }m)                                            |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  4 }        hl_id = hl_id_cell                    |
        {2:  5 }    end                                       |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|
                                                          |
      ]],
    }
    screen:expect(not_concealed)
    api.nvim_buf_set_extmark(0, ns, 1, 0, { conceal_lines = '' })
    screen:expect_unchanged()
    feed('j')
    local concealed = {
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }^    if hl_id_cell ~= nil then                 |
        {2:  4 }        hl_id = hl_id_cell                    |
        {2:  5 }    end                                       |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*3
                                                          |
      ]],
    }
    screen:expect(concealed)
    feed('k')
    screen:expect(not_concealed)
    exec('set concealcursor=n')
    screen:expect(concealed)
    api.nvim_buf_set_extmark(0, ns, 3, 0, { conceal_lines = '' })
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }^    if hl_id_cell ~= nil then                 |
        {2:  5 }    end                                       |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*4
                                                          |
      ]],
    })
    feed('kjj')
    screen:expect_unchanged()
    api.nvim_buf_set_extmark(0, ns, 4, 0, { conceal_lines = '' })
    feed('kjjjC')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  5 }^                                              |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*4
        {24:-- INSERT --}                                      |
      ]],
    })
    feed('<esc>')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  6 }^    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*5
                                                          |
      ]],
    })
    feed('kji')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  5 }^                                              |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*4
        {24:-- INSERT --}                                      |
      ]],
    })
    feed('conceal text')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  5 }conceal text^                                  |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*4
        {24:-- INSERT --}                                      |
      ]],
    })
    feed('<esc>')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  6 }    for _ =^ 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*5
                                                          |
      ]],
    })
    feed('ggzfj')
    screen:expect({
      grid = [[
        {2:  1 }{33:^+--  2 lines: for _,item in ipairs(items) do··}|
        {2:  3 }    if hl_id_cell ~= nil then                 |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*5
                                                          |
      ]],
    })
    feed('j')
    screen:expect({
      grid = [[
        {2:  1 }{33:+--  2 lines: for _,item in ipairs(items) do··}|
        {2:  3 }^    if hl_id_cell ~= nil then                 |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*5
                                                          |
      ]],
    })
    feed('ggzdjzfj')
    screen:expect({
      grid = [[
        {2:  1 }for _,item in ipairs(items) do                |
        {2:  6 }^    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*6
                                                          |
      ]],
    })
    feed('jj')
    screen:expect({
      grid = [[
        {2:  1 }^for _,item in ipairs(items) do                |
        {2:  6 }    for _ = 1, (count or 1) do                |
        {2:  7 }        local cell = line[colpos]             |
        {2:  8 }        cell.text = text                      |
        {2:  9 }        cell.hl_id = hl_id                    |
        {2: 10 }        colpos = colpos+1                     |
        {2: 11 }    end                                       |
        {2: 12 }end                                           |
        {1:~                                                 }|*6
                                                          |
      ]],
    })
    -- Below virtual line belonging to line above concealed line is drawn.
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'line 1 below' } } } })
    -- Above virtual line belonging to concealed line isn't.
    api.nvim_buf_set_extmark(0, ns, 1, 0, {
      virt_lines = { { { 'line 2 above' } } },
      virt_lines_above = true,
    })
    screen:expect([[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:    }line 1 below                                  |
      {2:  6 }^    for _ = 1, (count or 1) do                |
      {2:  7 }        local cell = line[colpos]             |
      {2:  8 }        cell.text = text                      |
      {2:  9 }        cell.hl_id = hl_id                    |
      {2: 10 }        colpos = colpos+1                     |
      {2: 11 }    end                                       |
      {2: 12 }end                                           |
      {1:~                                                 }|*5
                                                        |
    ]])
    -- w_lines.wl_lastlnum values are valid
    command('set relativenumber concealcursor=')
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 1, 0, { conceal_lines = '' })
    api.nvim_buf_set_extmark(0, ns, 4, 0, { conceal_lines = '' })
    feed('zE')
    screen:expect([[
      {2:  4 }for _,item in ipairs(items) do                |
      {2:  2 }    if hl_id_cell ~= nil then                 |
      {2:  1 }        hl_id = hl_id_cell                    |
      {2:5   }^conceal text                                  |
      {2:  1 }    for _ = 1, (count or 1) do                |
      {2:  2 }        local cell = line[colpos]             |
      {2:  3 }        cell.text = text                      |
      {2:  4 }        cell.hl_id = hl_id                    |
      {2:  5 }        colpos = colpos+1                     |
      {2:  6 }    end                                       |
      {2:  7 }end                                           |
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('jj')
    screen:expect([[
      {2:  6 }for _,item in ipairs(items) do                |
      {2:  4 }    if hl_id_cell ~= nil then                 |
      {2:  3 }        hl_id = hl_id_cell                    |
      {2:  1 }    for _ = 1, (count or 1) do                |
      {2:7   }^        local cell = line[colpos]             |
      {2:  1 }        cell.text = text                      |
      {2:  2 }        cell.hl_id = hl_id                    |
      {2:  3 }        colpos = colpos+1                     |
      {2:  4 }    end                                       |
      {2:  5 }end                                           |
      {1:~                                                 }|*4
                                                        |
    ]])
    -- Correct relativenumber for line below concealed line #33694
    feed('4Gk')
    screen:expect([[
      {2:  2 }for _,item in ipairs(items) do                |
      {2:3   }    if h^l_id_cell ~= nil then                 |
      {2:  1 }        hl_id = hl_id_cell                    |
      {2:  3 }    for _ = 1, (count or 1) do                |
      {2:  4 }        local cell = line[colpos]             |
      {2:  5 }        cell.text = text                      |
      {2:  6 }        cell.hl_id = hl_id                    |
      {2:  7 }        colpos = colpos+1                     |
      {2:  8 }    end                                       |
      {2:  9 }end                                           |
      {1:~                                                 }|*4
                                                        |
    ]])
    -- Also with above virtual line #32744
    command('set nornu')
    api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { 'virt_below 4' } } } })
    feed('6G')
    screen:expect([[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  3 }    if hl_id_cell ~= nil then                 |
      {2:  4 }        hl_id = hl_id_cell                    |
      {2:    }virt_below 4                                  |
      {2:  6 }    ^for _ = 1, (count or 1) do                |
      {2:  7 }        local cell = line[colpos]             |
      {2:  8 }        cell.text = text                      |
      {2:  9 }        cell.hl_id = hl_id                    |
      {2: 10 }        colpos = colpos+1                     |
      {2: 11 }    end                                       |
      {2: 12 }end                                           |
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('j')
    screen:expect([[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  3 }    if hl_id_cell ~= nil then                 |
      {2:  4 }        hl_id = hl_id_cell                    |
      {2:    }virt_below 4                                  |
      {2:  6 }    for _ = 1, (count or 1) do                |
      {2:  7 }    ^    local cell = line[colpos]             |
      {2:  8 }        cell.text = text                      |
      {2:  9 }        cell.hl_id = hl_id                    |
      {2: 10 }        colpos = colpos+1                     |
      {2: 11 }    end                                       |
      {2: 12 }end                                           |
      {1:~                                                 }|*3
                                                        |
    ]])
    -- Even when virtual line is added as line is concealed #32762
    feed('5G')
    api.nvim_buf_clear_namespace(0, ns, 3, 4)
    feed('j')
    api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { 'virt_below 4' } } } })
    screen:expect([[
      {2:  1 }for _,item in ipairs(items) do                |
      {2:  3 }    if hl_id_cell ~= nil then                 |
      {2:  4 }        hl_id = hl_id_cell                    |
      {2:    }virt_below 4                                  |
      {2:  6 }^    for _ = 1, (count or 1) do                |
      {2:  7 }        local cell = line[colpos]             |
      {2:  8 }        cell.text = text                      |
      {2:  9 }        cell.hl_id = hl_id                    |
      {2: 10 }        colpos = colpos+1                     |
      {2: 11 }    end                                       |
      {2: 12 }end                                           |
      {1:~                                                 }|*3
                                                        |
    ]])
    -- No scrolling for concealed topline #33033
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines_above = true, virt_lines = { { { 'virt_above 2' } } } })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { conceal_lines = '' })
    feed('ggjj')
    screen:expect([[
      {2:    }virt_above 2                                  |
      {2:  2 }    local text, hl_id_cell, count = unpack(ite|
      {2:    }m)                                            |
      {2:  3 }^    if hl_id_cell ~= nil then                 |
      {2:  4 }        hl_id = hl_id_cell                    |
      {2:  5 }conceal text                                  |
      {2:  6 }    for _ = 1, (count or 1) do                |
      {2:  7 }        local cell = line[colpos]             |
      {2:  8 }        cell.text = text                      |
      {2:  9 }        cell.hl_id = hl_id                    |
      {2: 10 }        colpos = colpos+1                     |
      {2: 11 }    end                                       |
      {2: 12 }end                                           |
      {1:~                                                 }|
                                                        |
    ]])
    -- No asymmetric topline for <C-E><C-Y> #33182
    feed('4<C-E>')
    exec('set concealcursor=n')
    api.nvim_buf_set_extmark(0, ns, 4, 0, { conceal_lines = '' })
    eq(5, n.fn.line('w0'))
    feed('<C-E><C-Y>')
    eq(5, n.fn.line('w0'))
  end)

  it('conceal_lines not checking on invalid row #36057', function()
    exec_lua(function()
      vim.fn.setline(1, { 'foo', 'bar', 'baz' })
      vim.api.nvim_command('set conceallevel=3 scrolloff=3')
      vim.api.nvim_open_win(0, true, { width = 1, height = 1, relative = 'editor', row = 0, col = 0 })
      vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { conceal_lines = '' })
      vim.api.nvim__redraw({ flush = true })
    end)
    n.assert_alive()
  end)

  it('redraws the line from which a left gravity mark has moved #27369', function()
    fn.setline(1, { 'aaa', 'bbb', 'ccc', 'ddd' })
    api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = { { 'foo' } }, right_gravity = false })
    feed('yyp')
    screen:expect([[
      aaa                                               |
      ^aaa foo                                           |
      bbb                                               |
      ccc                                               |
      ddd                                               |
      {1:~                                                 }|*9
                                                        |
    ]])
  end)

  it('redraws extmark that starts and ends outside the screen', function()
    local lines = vim.split(('1'):rep(20), '', { plain = true })
    api.nvim_buf_set_lines(0, 0, -1, true, lines)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'ErrorMsg', end_row = 19, end_col = 0 })
    screen:expect({
      grid = [[
        {4:^1}                                                 |
        {4:1}                                                 |*13
                                                          |
      ]],
    })
    feed('<C-e>')
    -- Newly visible line should also have the highlight.
    screen:expect({
      grid = [[
        {4:^1}                                                 |
        {4:1}                                                 |*13
                                                          |
      ]],
    })
  end)

  it('line("w$", win) considers conceal_lines', function()
    api.nvim_buf_set_lines(0, 0, -1, true, { 'line 1', 'line 2', 'line 3' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { conceal_lines = '' }) -- conceal line 1

    local win = exec_lua(function()
      local provider_ns = vim.api.nvim_create_namespace('test_f_line')
      _G.line_w_dollar = {}
      vim.api.nvim_set_decoration_provider(provider_ns, {
        on_start = function()
          for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
            table.insert(_G.line_w_dollar, { win, vim.fn.line('w$', win) })
          end
        end,
      })

      local win = vim.api.nvim_open_win(0, false, {
        relative = 'editor',
        width = 20,
        height = 1,
        row = 0,
        col = 0,
        border = 'single',
      })
      vim.api.nvim_set_option_value('conceallevel', 2, { scope = 'local', win = win })

      return win
    end)

    local line_w_dollar = exec_lua('return _G.line_w_dollar')
    for _, win_line in ipairs(line_w_dollar) do
      if win_line[1] == win then
        eq(2, win_line[2])
      end
    end
  end)
end)

describe('decorations: inline virtual text', function()
  local screen ---@type test.functional.ui.screen
  local ns ---@type integer
  before_each(function()
    clear()
    screen = Screen.new(50, 3)
    screen:set_default_attr_ids {
      [1] = { bold = true, foreground = Screen.colors.Blue },
      [2] = { foreground = Screen.colors.Brown },
      [3] = { bold = true, foreground = Screen.colors.SeaGreen },
      [4] = { background = Screen.colors.Red1, foreground = Screen.colors.Gray100 },
      [5] = { background = Screen.colors.Red1, bold = true },
      [6] = { foreground = Screen.colors.DarkCyan },
      [7] = { background = Screen.colors.LightGrey, foreground = Screen.colors.Black },
      [8] = { bold = true },
      [9] = { background = Screen.colors.Plum1 },
      [10] = { foreground = Screen.colors.SlateBlue },
      [11] = { blend = 30, background = Screen.colors.Red1 },
      [12] = { background = Screen.colors.Yellow },
      [13] = { reverse = true },
      [14] = { foreground = Screen.colors.SlateBlue, background = Screen.colors.LightMagenta },
      [15] = { bold = true, reverse = true },
      [16] = { foreground = Screen.colors.Red },
      [17] = { background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue },
      [18] = { background = Screen.colors.LightGrey, foreground = Screen.colors.Red },
      [19] = { background = Screen.colors.Yellow, foreground = Screen.colors.SlateBlue },
      [20] = { background = Screen.colors.LightGrey, foreground = Screen.colors.SlateBlue },
      [21] = { reverse = true, foreground = Screen.colors.SlateBlue },
      [22] = { background = Screen.colors.Gray90 },
      [23] = { background = Screen.colors.Gray90, foreground = Screen.colors.Blue, bold = true },
    }

    ns = api.nvim_create_namespace 'test'
  end)

  it('works', function()
    screen:try_resize(50, 10)
    insert(example_text)
    feed 'gg'
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
              cell.hl_id = hl_id                        |
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 1, 14, { virt_text = { { ': ', 'Special' }, { 'string', 'Type' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
          local text{10:: }{3:string}, hl_id_cell, count = unpack|
      (item)                                            |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
                                                        |
    ]],
    }

    screen:try_resize(55, 10)
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                         |
          local text{10:: }{3:string}, hl_id_cell, count = unpack(item|
      )                                                      |
          if hl_id_cell ~= nil then                          |
              hl_id = hl_id_cell                             |
          end                                                |
          for _ = 1, (count or 1) do                         |
              local cell = line[colpos]                      |
              cell.text = text                               |
                                                             |
    ]],
    }

    screen:try_resize(56, 10)
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                          |
          local text{10:: }{3:string}, hl_id_cell, count = unpack(item)|
          if hl_id_cell ~= nil then                           |
              hl_id = hl_id_cell                              |
          end                                                 |
          for _ = 1, (count or 1) do                          |
              local cell = line[colpos]                       |
              cell.text = text                                |
              cell.hl_id = hl_id                              |
                                                              |
    ]],
    }
  end)

  it('works with 0-width chunk', function()
    screen:try_resize(50, 10)
    insert(example_text)
    feed 'gg'
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
          local text, hl_id_cell, count = unpack(item)  |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
              cell.hl_id = hl_id                        |
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 0, 5, { virt_text = { { '' }, { '' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 1, 14, { virt_text = { { '' }, { ': ', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 1, 48, { virt_text = { { '' }, { '' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      ^for _,item in ipairs(items) do                    |
          local text{10:: }, hl_id_cell, count = unpack(item)|
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
              cell.hl_id = hl_id                        |
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 1, 14, { virt_text = { { '' }, { 'string', 'Type' } }, virt_text_pos = 'inline' })
    feed('V')
    screen:expect {
      grid = [[
      ^f{7:or _,item in ipairs(items) do}                    |
          local text{10:: }{3:string}, hl_id_cell, count = unpack|
      (item)                                            |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
      {8:-- VISUAL LINE --}                                 |
    ]],
    }

    feed('<Esc>jf,')
    screen:expect {
      grid = [[
      for _,item in ipairs(items) do                    |
          local text{10:: }{3:string}^, hl_id_cell, count = unpack|
      (item)                                            |
          if hl_id_cell ~= nil then                     |
              hl_id = hl_id_cell                        |
          end                                           |
          for _ = 1, (count or 1) do                    |
              local cell = line[colpos]                 |
              cell.text = text                          |
                                                        |
    ]],
    }
  end)

  it('Normal mode "gM" command works properly', function()
    command([[call setline(1, '123456789')]])
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 7, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
    feed('gM')
    screen:expect {
      grid = [[
      12{10:bbb}34^567{10:bbb}89                                   |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  local function test_normal_gj_gk()
    screen:try_resize(60, 6)
    command([[call setline(1, repeat([repeat('a', 55)], 2))]])
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { ('b'):rep(10), 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 1, 40, { virt_text = { { ('b'):rep(10), 'Special' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gj')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      ^aaaaa                                                       |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gj')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gj')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      ^aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gk')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gk')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      ^aaaaa                                                       |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('gk')
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
      aaaaa                                                       |
      {1:~                                                           }|
                                                                  |
    ]],
    }
  end

  describe('Normal mode "gj" "gk" commands work properly', function()
    it('with virtualedit=', function()
      test_normal_gj_gk()
    end)

    it('with virtualedit=all', function()
      command('set virtualedit=all')
      test_normal_gj_gk()
    end)
  end)

  it('cursor positions are correct with multiple inline virtual text', function()
    insert('12345678')
    api.nvim_buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
    feed '^'
    feed '4l'
    screen:expect {
      grid = [[
      1234{10: virtual text  virtual text }^5678              |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('adjusts cursor location correctly when inserting around inline virtual text', function()
    insert('12345678')
    feed '$'
    api.nvim_buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })

    screen:expect {
      grid = [[
      1234{10: virtual text }567^8                            |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('has correct highlighting with multi-byte characters', function()
    insert('12345678')
    api.nvim_buf_set_extmark(0, ns, 0, 4, { virt_text = { { 'múlti-byté chñröcters 修补', 'Special' } }, virt_text_pos = 'inline' })

    screen:expect {
      grid = [[
      1234{10:múlti-byté chñröcters 修补}567^8                |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('has correct cursor position when inserting around virtual text', function()
    insert('12345678')
    api.nvim_buf_set_extmark(0, ns, 0, 4, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    feed '^'
    feed '3l'
    feed 'a'
    screen:expect {
      grid = [[
      1234{10:^virtual text}5678                              |
      {1:~                                                 }|
      {8:-- INSERT --}                                      |
    ]],
    }
    feed '<ESC>'
    screen:expect {
      grid = [[
      123^4{10:virtual text}5678                              |
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed '^'
    feed '4l'
    feed 'i'
    screen:expect {
      grid = [[
      1234{10:^virtual text}5678                              |
      {1:~                                                 }|
      {8:-- INSERT --}                                      |
    ]],
    }
  end)

  it('has correct cursor position with virtual text on an empty line', function()
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      {10:^virtual text}                                      |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('text is drawn correctly with a wrapping virtual text', function()
    screen:try_resize(60, 8)
    exec([[
      call setline(1, ['', 'aaa', '', 'bbbbbb'])
      normal gg0
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('X', 60), 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = { { string.rep('X', 61), 'Special' } }, virt_text_pos = 'inline' })
    feed('$')
    screen:expect {
      grid = [[
      {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      aaa                                                         |
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      bbbbbb                                                      |
      {1:~                                                           }|*2
                                                                  |
    ]],
    }
    feed('j')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      aa^a                                                         |
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      bbbbbb                                                      |
      {1:~                                                           }|*2
                                                                  |
    ]],
    }
    feed('j')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      aaa                                                         |
      {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      bbbbbb                                                      |
      {1:~                                                           }|*2
                                                                  |
    ]],
    }
    feed('j')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      aaa                                                         |
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      bbbbb^b                                                      |
      {1:~                                                           }|*2
                                                                  |
    ]],
    }
    feed('0<C-V>2l2k')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {7:aa}^a                                                         |
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      {7:bbb}bbb                                                      |
      {1:~                                                           }|*2
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed([[<Esc>/aaa\n\%V<CR>]])
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {12:^aaa }                                                        |
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:X}                                                           |
      bbbbbb                                                      |
      {1:~                                                           }|*2
      {16:search hit BOTTOM, continuing at TOP}                        |
    ]],
    }
    feed('3ggic')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {12:aaa }                                                        |
      c{10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:XX}                                                          |
      bbbbbb                                                      |
      {1:~                                                           }|*2
      {8:-- INSERT --}                                                |
    ]],
    }
    feed([[<Esc>/aaa\nc\%V<CR>]])
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {12:^aaa }                                                        |
      {12:c}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:XX}                                                          |
      bbbbbb                                                      |
      {1:~                                                           }|*2
      {16:search hit BOTTOM, continuing at TOP}                        |
    ]],
    }
  end)

  it('cursor position is correct with virtual text attached to hard TABs', function()
    command('set noexpandtab')
    feed('i')
    feed('<TAB>')
    feed('<TAB>')
    feed('test')
    feed('<ESC>')
    api.nvim_buf_set_extmark(0, ns, 0, 1, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    feed('0')
    screen:expect {
      grid = [[
             ^ {10:virtual text}    test                      |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('l')
    screen:expect {
      grid = [[
              {10:virtual text}   ^ test                      |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('l')
    screen:expect {
      grid = [[
              {10:virtual text}    ^test                      |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('l')
    screen:expect {
      grid = [[
              {10:virtual text}    t^est                      |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('l')
    screen:expect {
      grid = [[
              {10:virtual text}    te^st                      |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('cursor position is correct with virtual text on an empty line', function()
    command('set linebreak')
    insert('one twoword')
    feed('0')
    api.nvim_buf_set_extmark(0, ns, 0, 3, { virt_text = { { ': virtual text', 'Special' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      ^one{10:: virtual text} twoword                         |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('search highlight is correct', function()
    insert('foo foo foo bar\nfoo foo foo bar')
    feed('gg0')
    api.nvim_buf_set_extmark(0, ns, 0, 9, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 9, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 9, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 9, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    screen:expect {
      grid = [[
      ^foo foo f{10:AAABBB}oo bar                             |
      foo foo f{10:CCCDDD}oo bar                             |
                                                        |
    ]],
    }

    feed('/foo')
    screen:expect {
      grid = [[
      {12:foo} {13:foo} {12:f}{10:AAA}{19:BBB}{12:oo} bar                             |
      {12:foo} {12:foo} {12:f}{19:CCC}{10:DDD}{12:oo} bar                             |
      /foo^                                              |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 0, 13, { virt_text = { { 'EEE', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    feed('<C-G>')
    screen:expect {
      grid = [[
      {12:foo} {12:foo} {13:f}{10:AAA}{21:BBB}{13:oo} b{10:EEE}ar                          |
      {12:foo} {12:foo} {12:f}{19:CCC}{10:DDD}{12:oo} bar                             |
      /foo^                                              |
    ]],
    }
  end)

  it('Visual select highlight is correct', function()
    insert('foo foo foo bar\nfoo foo foo bar')
    feed('gg0')
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    feed('8l')
    screen:expect {
      grid = [[
      foo foo {10:AAABBB}^foo bar                             |
      foo foo {10:CCCDDD}foo bar                             |
                                                        |
    ]],
    }

    feed('<C-V>')
    feed('2hj')
    screen:expect {
      grid = [[
      foo fo{7:o }{10:AAA}{20:BBB}{7:f}oo bar                             |
      foo fo^o{7: }{20:CCC}{10:DDD}{7:f}oo bar                             |
      {8:-- VISUAL BLOCK --}                                |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 0, 10, { virt_text = { { 'EEE', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    screen:expect {
      grid = [[
      foo fo{7:o }{10:AAA}{20:BBB}{7:f}o{10:EEE}o bar                          |
      foo fo^o{7: }{20:CCC}{10:DDD}{7:f}oo bar                             |
      {8:-- VISUAL BLOCK --}                                |
    ]],
    }
  end)

  it('inside highlight range of another extmark', function()
    insert('foo foo foo bar\nfoo foo foo bar')
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    api.nvim_buf_set_extmark(0, ns, 0, 4, { end_col = 11, hl_group = 'Search' })
    api.nvim_buf_set_extmark(0, ns, 1, 4, { end_col = 11, hl_group = 'Search' })
    screen:expect {
      grid = [[
      foo {12:foo }{10:AAA}{19:BBB}{12:foo} bar                             |
      foo {12:foo }{19:CCC}{10:DDD}{12:foo} ba^r                             |
                                                        |
    ]],
    }
  end)

  it('inside highlight range of syntax', function()
    insert('foo foo foo bar\nfoo foo foo bar')
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    command([[syntax match Search 'foo \zsfoo foo\ze bar']])
    screen:expect {
      grid = [[
      foo {12:foo }{10:AAA}{19:BBB}{12:foo} bar                             |
      foo {12:foo }{19:CCC}{10:DDD}{12:foo} ba^r                             |
                                                        |
    ]],
    }
  end)

  it('cursor position is correct when inserting around a virtual text with left gravity', function()
    screen:try_resize(27, 4)
    insert(('a'):rep(15))
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { ('>'):rep(43), 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
    command('setlocal showbreak=+ breakindent breakindentopt=shift:2')
    feed('08l')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}^aaaaaaa                 |
                                 |
    ]],
    }
    feed('i')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}^aaaaaaa                 |
      {8:-- INSERT --}               |
    ]],
    }
    feed([[<C-\><C-O>]])
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}^aaaaaaa                 |
      {8:-- (insert) --}             |
    ]],
    }
    feed('D')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
      {1:^~                          }|
      {8:-- INSERT --}               |
    ]],
    }
    command('setlocal list listchars=eol:$')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+^$}                       |
      {8:-- INSERT --}               |
    ]],
    }
    feed('<C-U>')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>}{1:^$}       |
      {1:~                          }|
      {8:-- INSERT --}               |
    ]],
    }
    feed('a')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>}a{1:^$}      |
      {1:~                          }|
      {8:-- INSERT --}               |
    ]],
    }
    feed('<Esc>')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>}^a{1:$}      |
      {1:~                          }|
                                 |
    ]],
    }
    feed('x')
    screen:expect {
      grid = [[
      {10:^>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>>>>>>>}{1:$}       |
      {1:~                          }|
                                 |
    ]],
    }
  end)

  it('cursor position is correct when inserting around virtual texts with both left and right gravity', function()
    screen:try_resize(30, 4)
    command('setlocal showbreak=+ breakindent breakindentopt=shift:2')
    insert(('a'):rep(15))
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { ('>'):rep(32), 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
    api.nvim_buf_set_extmark(0, ns, 0, 8, { virt_text = { { ('<'):rep(32), 'Special' } }, virt_text_pos = 'inline', right_gravity = true })
    feed('08l')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>><<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<}^aaaaaaa     |
                                    |
    ]],
    }
    feed('i')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>^<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<}aaaaaaa     |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('a')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>}a{10:^<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<<}aaaaaaa    |
      {8:-- INSERT --}                  |
    ]],
    }
    feed([[<C-\><C-O>]])
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>}a{10:<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<<}^aaaaaaa    |
      {8:-- (insert) --}                |
    ]],
    }
    feed('D')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>}a{10:^<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<<}           |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<BS>')
    screen:expect {
      grid = [[
      aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>>>>>>>>>^<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<<<<<<<<}            |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<C-U>')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>^<<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<}                    |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('a')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>}a{10:^<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<}                   |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<Esc>')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>}^a{10:<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<<}                   |
                                    |
    ]],
    }
    feed('x')
    screen:expect {
      grid = [[
      {10:^>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>><<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<}                    |
                                    |
    ]],
    }
    feed('i')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:>>^<<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<<<<<}                    |
      {8:-- INSERT --}                  |
    ]],
    }
    screen:try_resize(32, 4)
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
        {1:+}{10:^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<}|
        {1:+}{10:<<<}                          |
      {8:-- INSERT --}                    |
    ]],
    }
    command('setlocal nobreakindent')
    screen:expect {
      grid = [[
      {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
      {1:+}{10:^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<}|
      {1:+}{10:<}                              |
      {8:-- INSERT --}                    |
    ]],
    }
  end)

  it('draws correctly with no wrap multiple virtual text, where one is hidden', function()
    insert('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz')
    command('set nowrap')
    api.nvim_buf_set_extmark(0, ns, 0, 50, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    feed('$')
    screen:expect {
      grid = [[
      opqrstuvwxyzabcdefghijklmnopqrstuvwx{10:virtual text}y^z|
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('draws correctly with no wrap and a long virtual text', function()
    insert('abcdefghi')
    command('set nowrap')
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
    feed('$')
    screen:expect([[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
      {1:~                                                 }|
                                                        |
    ]])
    command('set list listchars+=precedes:!')
    screen:expect([[
      {1:!}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
      {1:~                                                 }|
                                                        |
    ]])
  end)

  it('draws correctly with no wrap and multibyte virtual text', function()
    insert('12345678')
    command('set nowrap')
    api.nvim_buf_set_extmark(0, ns, 0, 2, {
      hl_mode = 'replace',
      virt_text = { { 'α口β̳γ̲=', 'Special' }, { '❤️', 'Special' } },
      virt_text_pos = 'inline',
    })
    screen:expect([[
      12{10:α口β̳γ̲=❤️}34567^8                                  |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      2{10:α口β̳γ̲=❤️}34567^8                                   |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:α口β̳γ̲=❤️}34567^8                                    |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:口β̳γ̲=❤️}34567^8                                     |
      {1:~                                                 }|
                                                        |
    ]])
    feed('V')
    screen:expect([[
      {10:口β̳γ̲=❤️}{7:34567}^8                                     |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]])
    command('set list listchars+=precedes:!')
    screen:expect([[
      {1:!<}{10:β̳γ̲=❤️}{7:34567}^8                                     |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]])
    feed('zl')
    screen:expect([[
      {1:!}{10:β̳γ̲=❤️}{7:34567}^8                                      |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]])
    command('set nolist')
    screen:expect([[
      {1:<}{10:β̳γ̲=❤️}{7:34567}^8                                      |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]])
    feed('<Esc>')
    screen:expect([[
      {1:<}{10:β̳γ̲=❤️}34567^8                                      |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:β̳γ̲=❤️}34567^8                                       |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:γ̲=❤️}34567^8                                        |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:=❤️}34567^8                                         |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {10:❤️}34567^8                                          |
      {1:~                                                 }|
                                                        |
    ]])
    command('set list')
    screen:expect([[
      {1:!<}34567^8                                          |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {1:!}34567^8                                           |
      {1:~                                                 }|
                                                        |
    ]])
    command('set nolist')
    screen:expect([[
      {1:<}34567^8                                           |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      34567^8                                            |
      {1:~                                                 }|
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      4567^8                                             |
      {1:~                                                 }|
                                                        |
    ]])
  end)

  it('tabs are the correct length with no wrap following virtual text', function()
    command('set nowrap')
    feed('itest<TAB>a<ESC>')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
    feed('gg$')
    screen:expect {
      grid = [[
      {10:aaaaaaaaaaaaaaaaaaaaaaaaa}test     ^a               |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('highlighting does not extend with no wrap and a long virtual text', function()
    insert('abcdef')
    command('set nowrap')
    api.nvim_buf_set_extmark(0, ns, 0, 3, { virt_text = { { string.rep('X', 50), 'Special' } }, virt_text_pos = 'inline' })
    feed('$')
    screen:expect {
      grid = [[
      {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}de^f|
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('hidden virtual text does not interfere with Visual highlight', function()
    insert('abcdef')
    command('set nowrap')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'XXX', 'Special' } }, virt_text_pos = 'inline' })
    feed('V2zl')
    screen:expect {
      grid = [[
      {10:X}{7:abcde}^f                                           |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {7:abcde}^f                                            |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      {7:bcde}^f                                             |
      {1:~                                                 }|
      {8:-- VISUAL LINE --}                                 |
    ]],
    }
  end)

  it('highlighting is correct when virtual text wraps with number', function()
    screen:try_resize(50, 5)
    insert([[
    test
    test]])
    command('set number')
    api.nvim_buf_set_extmark(0, ns, 0, 1, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
    feed('gg0')
    screen:expect {
      grid = [[
      {2:  1 }^t{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {2:    }{10:XXXXXXXXXX}est                                 |
      {2:  2 }test                                          |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('highlighting is correct when virtual text is proceeded with a match', function()
    insert([[test]])
    api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
    feed('gg0')
    command('match ErrorMsg /e/')
    screen:expect {
      grid = [[
      ^t{4:e}{10:virtual text}st                                  |
      {1:~                                                 }|
                                                        |
    ]],
    }
    command('match ErrorMsg /s/')
    screen:expect {
      grid = [[
      ^te{10:virtual text}{4:s}t                                  |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('smoothscroll works correctly when virtual text wraps', function()
    insert('foobar')
    api.nvim_buf_set_extmark(0, ns, 0, 3, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
    command('setlocal smoothscroll')
    screen:expect {
      grid = [[
      foo{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
      {10:XXXXXXXX}ba^r                                       |
                                                        |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}{10:XXXXX}ba^r                                       |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('in diff mode is highlighted correct', function()
    screen:try_resize(50, 10)
    insert([[
    9000
    0009
    0009
    9000
    0009
    ]])
    insert('aaa\tbbb')
    command('set diff')
    api.nvim_buf_set_extmark(0, ns, 0, 1, { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
    api.nvim_buf_set_extmark(0, ns, 5, 0, { virt_text = { { '!', 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 5, 3, { virt_text = { { '' } }, virt_text_pos = 'inline' })
    command('vnew')
    insert([[
    000
    000
    000
    000
    000
    ]])
    insert('aaabbb')
    command('set diff')
    feed('gg0')
    screen:expect {
      grid = [[
      {9:^000                      }│{5:9}{14:test}{9:000                }|
      {9:000                      }│{9:000}{5:9}{9:                    }|*2
      {9:000                      }│{5:9}{9:000                    }|
      {9:000                      }│{9:000}{5:9}{9:                    }|
      {9:aaabbb                   }│{14:!}{9:aaa}{5:    }{9:bbb             }|
      {1:~                        }│{1:~                       }|*2
      {15:[No Name] [+]             }{13:[No Name] [+]           }|
                                                        |
    ]],
    }
    command('wincmd w | set nowrap')
    feed('zl')
    screen:expect {
      grid = [[
      {9:000                      }│{14:test}{9:000                 }|
      {9:000                      }│{9:00}{5:9}{9:                     }|*2
      {9:000                      }│{9:000                     }|
      {9:000                      }│{9:00}{5:9}{9:                     }|
      {9:aaabbb                   }│{9:aaa}{5:    }{9:bb^b              }|
      {1:~                        }│{1:~                       }|*2
      {13:[No Name] [+]             }{15:[No Name] [+]           }|
                                                        |
    ]],
    }
  end)

  it('correctly draws when there are multiple overlapping virtual texts on the same line with nowrap', function()
    command('set nowrap')
    insert('a')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('b', 55), 'Special' } }, virt_text_pos = 'inline' })
    feed('$')
    screen:expect {
      grid = [[
      {10:bbbbbbbbbbbbbbbbbbbbbbbbb}^a                        |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('correctly draws when overflowing virtual text is followed by TAB with no wrap', function()
    command('set nowrap')
    feed('i<TAB>test<ESC>')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('a', 60), 'Special' } }, virt_text_pos = 'inline' })
    feed('0')
    screen:expect({
      grid = [[
      {10:aaaaaaaaaaaaaaaaaaaaaa}   ^ test                    |
      {1:~                                                 }|
                                                        |
    ]],
    })
  end)

  it('does not crash at column 0 when folded in a wide window', function()
    screen:try_resize(82, 5)
    command('hi! CursorLine guibg=NONE guifg=Red gui=NONE')
    command('set cursorline')
    insert([[
      aaaaa
      bbbbb

      ccccc]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'foo' } }, virt_text_pos = 'inline' })
    api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = { { 'bar' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      fooaaaaa                                                                          |
      bbbbb                                                                             |
      bar                                                                               |
      {16:cccc^c                                                                             }|
                                                                                        |
    ]],
    }
    command('1,2fold')
    screen:expect {
      grid = [[
      {17:+--  2 lines: aaaaa·······························································}|
      bar                                                                               |
      {16:cccc^c                                                                             }|
      {1:~                                                                                 }|
                                                                                        |
    ]],
    }
    feed('2k')
    screen:expect {
      grid = [[
      {18:^+--  2 lines: aaaaa·······························································}|
      bar                                                                               |
      ccccc                                                                             |
      {1:~                                                                                 }|
                                                                                        |
    ]],
    }
    command('3,4fold')
    screen:expect {
      grid = [[
      {18:^+--  2 lines: aaaaa·······························································}|
      {17:+--  2 lines: ccccc·······························································}|
      {1:~                                                                                 }|*2
                                                                                        |
    ]],
    }
    feed('j')
    screen:expect {
      grid = [[
      {17:+--  2 lines: aaaaa·······························································}|
      {18:^+--  2 lines: ccccc·······························································}|
      {1:~                                                                                 }|*2
                                                                                        |
    ]],
    }
  end)

  it('does not crash at right edge of wide window #23848', function()
    screen:try_resize(82, 5)
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { ('a'):rep(82) }, { 'b' } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      b                                                                                 |
      {1:~                                                                                 }|*2
                                                                                        |
    ]],
    }
    command('set nowrap')
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      {1:~                                                                                 }|*3
                                                                                        |
    ]],
    }
    feed('82i0<Esc>0')
    screen:expect {
      grid = [[
      ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000|
      {1:~                                                                                 }|*3
                                                                                        |
    ]],
    }
    command('set wrap')
    screen:expect {
      grid = [[
      ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000|
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      b                                                                                 |
      {1:~                                                                                 }|
                                                                                        |
    ]],
    }
  end)

  it('lcs-extends is drawn with inline virtual text at end of screen line', function()
    exec([[
      setlocal nowrap list listchars=extends:!
      call setline(1, repeat('a', 51))
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 50, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
    feed('20l')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:!}|
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:!}|
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:b}{1:!}|
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bb}{1:!}|
      {1:~                                                 }|
                                                        |
    ]],
    }
    feed('zl')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbb}a|
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('lcs-extends is drawn with only inline virtual text offscreen', function()
    command('set nowrap')
    command('set list')
    command('set listchars+=extends:c')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline' })
    insert(string.rep('a', 50))
    feed('gg0')
    screen:expect {
      grid = [[
      ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:c}|
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('blockwise Visual highlight with double-width virtual text (replace)', function()
    screen:try_resize(60, 6)
    insert('123456789\n123456789\n123456789\n123456789')
    api.nvim_buf_set_extmark(0, ns, 1, 1, { virt_text = { { '-口-', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    api.nvim_buf_set_extmark(0, ns, 2, 2, { virt_text = { { '口', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
    feed('gg0')
    screen:expect {
      grid = [[
      ^123456789                                                   |
      1{10:-口-}23456789                                               |
      12{10:口}3456789                                                 |
      123456789                                                   |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('<C-V>3jl')
    screen:expect {
      grid = [[
      {7:12}3456789                                                   |
      {7:1}{10:-口-}23456789                                               |
      {7:12}{10:口}3456789                                                 |
      {7:1}^23456789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      {7:123}456789                                                   |
      {7:1}{10:-口-}23456789                                               |
      {7:12}{10:口}3456789                                                 |
      {7:12}^3456789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('4l')
    screen:expect {
      grid = [[
      {7:1234567}89                                                   |
      {7:1}{10:-口-}{7:23}456789                                               |
      {7:12}{10:口}{7:345}6789                                                 |
      {7:123456}^789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('Ol')
    screen:expect {
      grid = [[
      1{7:234567}89                                                   |
      1{10:-口-}{7:23}456789                                               |
      1{7:2}{10:口}{7:345}6789                                                 |
      1^2{7:34567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      12{7:34567}89                                                   |
      1{10:-口-}{7:23}456789                                               |
      12{10:口}{7:345}6789                                                 |
      12^3{7:4567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      123{7:4567}89                                                   |
      1{10:-口-}{7:23}456789                                               |
      12{10:口}{7:345}6789                                                 |
      123^4{7:567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
  end)

  it('blockwise Visual highlight with double-width virtual text (combine)', function()
    screen:try_resize(60, 6)
    insert('123456789\n123456789\n123456789\n123456789')
    api.nvim_buf_set_extmark(0, ns, 1, 1, { virt_text = { { '-口-', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    api.nvim_buf_set_extmark(0, ns, 2, 2, { virt_text = { { '口', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
    feed('gg0')
    screen:expect {
      grid = [[
      ^123456789                                                   |
      1{10:-口-}23456789                                               |
      12{10:口}3456789                                                 |
      123456789                                                   |
      {1:~                                                           }|
                                                                  |
    ]],
    }
    feed('<C-V>3jl')
    screen:expect {
      grid = [[
      {7:12}3456789                                                   |
      {7:1}{20:-}{10:口-}23456789                                               |
      {7:12}{10:口}3456789                                                 |
      {7:1}^23456789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      {7:123}456789                                                   |
      {7:1}{20:-口}{10:-}23456789                                               |
      {7:12}{20:口}3456789                                                 |
      {7:12}^3456789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('4l')
    screen:expect {
      grid = [[
      {7:1234567}89                                                   |
      {7:1}{20:-口-}{7:23}456789                                               |
      {7:12}{20:口}{7:345}6789                                                 |
      {7:123456}^789                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('Ol')
    screen:expect {
      grid = [[
      1{7:234567}89                                                   |
      1{20:-口-}{7:23}456789                                               |
      1{7:2}{20:口}{7:345}6789                                                 |
      1^2{7:34567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      12{7:34567}89                                                   |
      1{10:-}{20:口-}{7:23}456789                                               |
      12{20:口}{7:345}6789                                                 |
      12^3{7:4567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
    feed('l')
    screen:expect {
      grid = [[
      123{7:4567}89                                                   |
      1{10:-}{20:口-}{7:23}456789                                               |
      12{20:口}{7:345}6789                                                 |
      123^4{7:567}89                                                   |
      {1:~                                                           }|
      {8:-- VISUAL BLOCK --}                                          |
    ]],
    }
  end)

  local function test_virt_inline_showbreak_smoothscroll()
    screen:try_resize(30, 6)
    exec([[
      highlight! link LineNr Normal
      setlocal number showbreak=+ breakindent breakindentopt=shift:2
      setlocal scrolloff=0 smoothscroll
      call setline(1, repeat('a', 28))
      normal! $
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 27, { virt_text = { { ('123'):rep(23) } }, virt_text_pos = 'inline' })
    feed(':<CR>') -- Have a screen line that doesn't start with spaces
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
            {1:+}a1231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}12312312312312312312312|
            {1:+}3^a                     |
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}a1231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}12312312312312312312312|
            {1:+}3^a                     |
      {1:~                             }|
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}23123123123123123123123|
            {1:+}12312312312312312312312|
            {1:+}3^a                     |
      {1:~                             }|*2
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}12312312312312312312312|
            {1:+}3^a                     |
      {1:~                             }|*3
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}3^a                     |
      {1:~                             }|*4
      :                             |
    ]],
    }
    feed('zbi')
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
            {1:+}a^1231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}12312312312312312312312|
            {1:+}3a                     |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<BS>')
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
            {1:+}^12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}a                      |
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<Esc>l')
    feed(':<CR>') -- Have a screen line that doesn't start with spaces
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
            {1:+}12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|*2
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|*3
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}^a                      |
      {1:~                             }|*4
      :                             |
    ]],
    }
    feed('023x$')
    screen:expect {
      grid = [[
        1 aaa12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|*2
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}23123123123123123123123|
            {1:+}^a                      |
      {1:~                             }|*3
      :                             |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}^a                      |
      {1:~                             }|*4
      :                             |
    ]],
    }
    feed('zbi')
    screen:expect {
      grid = [[
        1 aaa^12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
            {1:+}a                      |
      {1:~                             }|
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<C-U>')
    screen:expect {
      grid = [[
        1 ^12312312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123a  |
      {1:~                             }|*2
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<Esc>')
    screen:expect {
      grid = [[
        1 12312312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123^a  |
      {1:~                             }|*2
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123^a  |
      {1:~                             }|*3
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
            {1:+}23123123123123123123^a  |
      {1:~                             }|*4
                                    |
    ]],
    }
    feed('zbx')
    screen:expect {
      grid = [[
        1 ^12312312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123   |
      {1:~                             }|*2
                                    |
    ]],
    }
    feed('26ia<Esc>a')
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
            {1:+}^12312312312312312312312|
            {1:+}31231231231231231231231|
            {1:+}23123123123123123123123|
      {1:~                             }|
      {8:-- INSERT --}                  |
    ]],
    }
    feed([[<C-\><C-O>:setlocal breakindentopt=<CR>]])
    screen:expect {
      grid = [[
        1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
          {1:+}^1231231231231231231231231|
          {1:+}2312312312312312312312312|
          {1:+}3123123123123123123      |
      {1:~                             }|
      {8:-- INSERT --}                  |
    ]],
    }
  end

  describe('with showbreak, smoothscroll', function()
    it('and cpoptions-=n', function()
      test_virt_inline_showbreak_smoothscroll()
    end)

    it('and cpoptions+=n', function()
      command('set cpoptions+=n')
      -- because of 'breakindent' the screen states are the same
      test_virt_inline_showbreak_smoothscroll()
    end)
  end)

  it('before TABs with smoothscroll', function()
    screen:try_resize(30, 6)
    exec([[
      setlocal list listchars=tab:<-> scrolloff=0 smoothscroll
      call setline(1, repeat("\t", 4) .. 'a')
      normal! $
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 3, { virt_text = { { ('12'):rep(32) } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      {1:<------><------><------>}121212|
      121212121212121212121212121212|
      1212121212121212121212121212{1:<-}|
      {1:----->}^a                       |
      {1:~                             }|
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}212121212121212121212121212|
      1212121212121212121212121212{1:<-}|
      {1:----->}^a                       |
      {1:~                             }|*2
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}2121212121212121212121212{1:<-}|
      {1:----->}^a                       |
      {1:~                             }|*3
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<-->}^a                       |
      {1:~                             }|*4
                                    |
    ]],
    }
    feed('zbh')
    screen:expect {
      grid = [[
      {1:<------><------><------>}121212|
      121212121212121212121212121212|
      1212121212121212121212121212{1:^<-}|
      {1:----->}a                       |
      {1:~                             }|
                                    |
    ]],
    }
    feed('i')
    screen:expect {
      grid = [[
      {1:<------><------><------>}^121212|
      121212121212121212121212121212|
      1212121212121212121212121212{1:<-}|
      {1:----->}a                       |
      {1:~                             }|
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<C-O>:setlocal nolist<CR>')
    screen:expect {
      grid = [[
                              ^121212|
      121212121212121212121212121212|
      1212121212121212121212121212  |
            a                       |
      {1:~                             }|
      {8:-- INSERT --}                  |
    ]],
    }
    feed('<Esc>l')
    screen:expect {
      grid = [[
                              121212|
      121212121212121212121212121212|
      1212121212121212121212121212  |
           ^ a                       |
      {1:~                             }|
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}212121212121212121212121212|
      1212121212121212121212121212  |
           ^ a                       |
      {1:~                             }|*2
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}2121212121212121212121212  |
           ^ a                       |
      {1:~                             }|*3
                                    |
    ]],
    }
    feed('<C-E>')
    screen:expect {
      grid = [[
      {1:<<<}  ^ a                       |
      {1:~                             }|*4
                                    |
    ]],
    }
  end)

  it('before a space with linebreak', function()
    screen:try_resize(50, 6)
    exec([[
      setlocal linebreak showbreak=+ breakindent breakindentopt=shift:2
      call setline(1, repeat('a', 50) .. ' ' .. repeat('c', 45))
      normal! $
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 50, { virt_text = { { ('b'):rep(10) } }, virt_text_pos = 'inline' })
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
        {1:+}bbbbbbbbbb                                     |
        {1:+}cccccccccccccccccccccccccccccccccccccccccccc^c  |
      {1:~                                                 }|*2
                                                        |
    ]],
    }
    feed('05x$')
    screen:expect {
      grid = [[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbb|
        {1:+}bbbbb                                          |
        {1:+}cccccccccccccccccccccccccccccccccccccccccccc^c  |
      {1:~                                                 }|*2
                                                        |
    ]],
    }
  end)

  it('before double-width char that wraps', function()
    exec([[
      call setline(1, repeat('a', 40) .. '口' .. '12345')
      normal! $
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { ('b'):rep(9) } }, virt_text_pos = 'inline' })
    screen:expect([[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}|
      口1234^5                                           |
                                                        |
    ]])
    feed('g0')
    screen:expect([[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}|
      ^口12345                                           |
                                                        |
    ]])
    command('set showbreak=+++')
    screen:expect([[
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}|
      {1:+++}^口12345                                        |
                                                        |
    ]])
  end)

  it('cursor position is correct if end_row or end_col is specified', function()
    screen:try_resize(50, 8)
    api.nvim_buf_set_lines(0, 0, -1, false, { ('a'):rep(48), ('b'):rep(48), ('c'):rep(48), ('d'):rep(48) })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 2, virt_text_pos = 'inline', virt_text = { { 'I1', 'NonText' } } })
    api.nvim_buf_set_extmark(0, ns, 3, 0, { end_col = 2, virt_text_pos = 'inline', virt_text = { { 'I2', 'NonText' } } })
    feed('$')
    screen:expect([[
      {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^a|
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  |
      cccccccccccccccccccccccccccccccccccccccccccccccc  |
      {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('j')
    screen:expect([[
      {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b  |
      cccccccccccccccccccccccccccccccccccccccccccccccc  |
      {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('j')
    screen:expect([[
      {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  |
      ccccccccccccccccccccccccccccccccccccccccccccccc^c  |
      {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('j')
    screen:expect([[
      {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  |
      cccccccccccccccccccccccccccccccccccccccccccccccc  |
      {1:I2}ddddddddddddddddddddddddddddddddddddddddddddddd^d|
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('is redrawn correctly after delete or redo #27370', function()
    screen:try_resize(50, 12)
    exec([[
      call setline(1, ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'])
      call setline(3, repeat('c', winwidth(0) - 1))
    ]])
    api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = { { '!!!' } }, virt_text_pos = 'inline' })
    feed('j')
    local before_delete = [[
      aaa                                               |
      !!!^bbb                                            |
      ccccccccccccccccccccccccccccccccccccccccccccccccc |
      ddd                                               |
      eee                                               |
      fff                                               |
      {1:~                                                 }|*5
                                                        |
    ]]
    screen:expect(before_delete)
    feed('dd')
    local after_delete = [[
      aaa                                               |
      !!!^ccccccccccccccccccccccccccccccccccccccccccccccc|
      cc                                                |
      ddd                                               |
      eee                                               |
      fff                                               |
      {1:~                                                 }|*5
                                                        |
    ]]
    screen:expect(after_delete)
    command('silent undo')
    screen:expect(before_delete)
    command('silent redo')
    screen:expect(after_delete)
    command('silent undo')
    screen:expect(before_delete)
    command('set report=100')
    feed('yypk2P')
    before_delete = [[
      aaa                                               |
      ^bbb                                               |
      bbb                                               |
      !!!bbb                                            |
      bbb                                               |
      ccccccccccccccccccccccccccccccccccccccccccccccccc |
      ddd                                               |
      eee                                               |
      fff                                               |
      {1:~                                                 }|*2
                                                        |
    ]]
    screen:expect(before_delete)
    feed('4dd')
    screen:expect(after_delete)
    command('silent undo')
    screen:expect(before_delete)
    command('silent redo')
    screen:expect(after_delete)
  end)

  it('cursor position is correct with invalidated inline virt text', function()
    screen:try_resize(50, 8)
    api.nvim_buf_set_lines(0, 0, -1, false, { ('a'):rep(48), ('b'):rep(48) })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text_pos = 'inline', virt_text = { { 'INLINE', 'NonText' } }, invalidate = true })
    screen:expect([[
      {1:INLINE}^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
      aaaa                                              |
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  |
      {1:~                                                 }|*4
                                                        |
    ]])
    feed('dd$')
    screen:expect([[
      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b  |
      {1:~                                                 }|*6
                                                        |
    ]])
  end)

  it('line size is correct with inline virt text at EOL and showbreak', function()
    screen:try_resize(50, 8)
    insert(('0123456789'):rep(5) .. '\nfoo\nbar')
    api.nvim_buf_set_extmark(0, ns, 0, 50, { virt_text = { { ('x'):rep(145), 'ErrorMsg' } }, virt_text_pos = 'inline' })

    command([[set cursorline scrolloff=0 showbreak=>\  smoothscroll]])
    screen:expect([[
      01234567890123456789012345678901234567890123456789|
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      {1:> }{4:x}                                               |
      foo                                               |
      {22:ba^r                                               }|
                                                        |
    ]])
    eq(5, api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }).all)
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      {1:> }{4:x}                                               |
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*2
      {1:> }{4:x}                                               |
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*2
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|
      {1:> }{4:x}                                               |
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:x}                                               |
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*4
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*5
                                                        |
    ]])

    feed('gg$xG$')
    screen:expect([[
      0123456789012345678901234567890123456789012345678{4:x}|
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|
                                                        |
    ]])
    eq(4, api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }).all)
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*2
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*2
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*4
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      foo                                               |
      {22:ba^r                                               }|
      {1:~                                                 }|*5
                                                        |
    ]])

    feed('zb')
    command('set list listchars=eol:$')
    screen:expect([[
      0123456789012345678901234567890123456789012345678{4:x}|
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      {1:> $}                                               |
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
                                                        |
    ]])
    eq(5, api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }).all)
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*3
      {1:> $}                                               |
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*2
      {1:> $}                                               |
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*2
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|
      {1:> $}                                               |
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> $}                                               |
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*4
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*5
                                                        |
    ]])

    feed('gg$xG$')
    screen:expect([[
      012345678901234567890123456789012345678901234567{4:xx}|
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*2
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}{1:$}|
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|
                                                        |
    ]])
    eq(4, api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }).all)
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|*2
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}{1:$}|
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*2
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}{1:$}|
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      {1:> }{4:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}{1:$}|
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*4
                                                        |
    ]])
    feed('<C-E>')
    screen:expect([[
      foo{1:$}                                              |
      {22:ba^r}{23:$}{22:                                              }|
      {1:~                                                 }|*5
                                                        |
    ]])
  end)

  it("virtcol('$') is correct with inline virt text at EOL", function()
    insert(('1234567890\n'):rep(6))
    for _, v in ipairs({ { 2, 'a' }, { 3, 'ab' }, { 4, 'abc' }, { 5, 'abcd' }, { 6, 'αβγ口' } }) do
      local ln, tx = unpack(v)
      local co = fn.col({ ln, '$' })
      eq(11, fn.virtcol({ ln, '$' }))
      api.nvim_buf_set_extmark(0, ns, ln - 1, co - 1, { virt_text = { { tx } }, virt_text_pos = 'inline' })
      eq(11 + fn.strwidth(tx), fn.virtcol({ ln, '$' }))
    end
  end)
end)

describe('decorations: virtual lines', function()
  local screen ---@type test.functional.ui.screen
  local ns ---@type integer

  before_each(function()
    clear()
    screen = Screen.new(50, 12)
    screen:add_extra_attr_ids {
      [100] = { foreground = Screen.colors.Blue, background = Screen.colors.Yellow },
    }

    ns = api.nvim_create_namespace 'test'
  end)

  local example_text2 = [[
if (h->n_buckets < new_n_buckets) { // expand
  khkey_t *new_keys = (khkey_t *)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t));
  h->keys = new_keys;
  if (kh_is_map && val_size) {
    char *new_vals = krealloc( h->vals_buf, new_n_buckets * val_size);
    h->vals_buf = new_vals;
  }
}]]

  it('works with one line', function()
    insert(example_text2)
    feed '2gg'
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        ^khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 1, 33, {
      virt_lines = { { { '>> ', 'NonText' }, { 'krealloc', 'Identifier' }, { ': change the size of an allocation' } } },
      virt_lines_above = true,
    })

    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
      {1:>> }{25:krealloc}: change the size of an allocation     |
        ^khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }

    feed '/krealloc<cr>'
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
      {1:>> }{25:krealloc}: change the size of an allocation     |
        khkey_t *new_keys = (khkey_t *){10:^krealloc}((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = {10:krealloc}( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
      /krealloc                                         |
    ]],
    }

    -- virtual line remains anchored to the extmark
    feed 'i<cr>'
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)                 |
      {1:>> }{25:krealloc}: change the size of an allocation     |
      {10:^krealloc}((void *)h->keys, new_n_buckets * sizeof(k|
      hkey_t));                                         |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = {10:krealloc}( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      {5:-- INSERT --}                                      |
    ]],
    }

    feed '<esc>3+'
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)                 |
      {1:>> }{25:krealloc}: change the size of an allocation     |
      {10:krealloc}((void *)h->keys, new_n_buckets * sizeof(k|
      hkey_t));                                         |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          ^char *new_vals = {10:krealloc}( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 5, 0, {
      virt_lines = { { { '^^ REVIEW:', 'Todo' }, { ' new_vals variable seems unnecessary?', 'Comment' } } },
    })
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)                 |
      {1:>> }{25:krealloc}: change the size of an allocation     |
      {10:krealloc}((void *)h->keys, new_n_buckets * sizeof(k|
      hkey_t));                                         |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          ^char *new_vals = {10:krealloc}( h->vals_buf, new_n_|
      buckets * val_size);                              |
      {100:^^ REVIEW:}{18: new_vals variable seems unnecessary?}   |
          h->vals_buf = new_vals;                       |
                                                        |
    ]],
    }

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)                 |
      {10:krealloc}((void *)h->keys, new_n_buckets * sizeof(k|
      hkey_t));                                         |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          ^char *new_vals = {10:krealloc}( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }
  end)

  it('works with text at the beginning of the buffer', function()
    insert(example_text2)
    feed 'gg'

    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }

    api.nvim_buf_set_extmark(0, ns, 0, 0, {
      virt_lines = {
        { { 'refactor(khash): ', 'Special' }, { 'take size of values as parameter' } },
        { { 'Author: Dev Devsson, ' }, { 'Tue Aug 31 10:13:37 2021', 'Comment' } },
      },
      virt_lines_above = true,
      right_gravity = false,
    })

    -- placing virt_text on topline does not automatically cause a scroll
    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
      {1:~                                                 }|
                                                        |
    ]],
      unchanged = true,
    }

    feed '<c-b>'
    screen:expect {
      grid = [[
      {16:refactor(khash): }take size of values as parameter |
      Author: Dev Devsson, {18:Tue Aug 31 10:13:37 2021}     |
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        ^}                                               |
                                                        |
    ]],
    }
  end)

  it('works with text at the end of the buffer', function()
    insert(example_text2)
    feed 'G'

    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }

    local id = api.nvim_buf_set_extmark(0, ns, 7, 0, {
      virt_lines = { { { 'Grugg' } } },
      right_gravity = false,
    })

    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      Grugg                                             |
                                                        |
    ]],
    }

    screen:try_resize(50, 11)
    feed('gg')
    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }

    feed('G<C-E>')
    screen:expect {
      grid = [[
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      Grugg                                             |
                                                        |
    ]],
    }

    feed('gg')
    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }

    screen:try_resize(50, 12)
    feed('G')
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      Grugg                                             |
                                                        |
    ]],
    }

    api.nvim_buf_del_extmark(0, ns, id)
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('works beyond end of the buffer with virt_lines_above', function()
    insert(example_text2)
    feed 'G'

    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      {1:~                                                 }|
                                                        |
    ]],
    }

    local id = api.nvim_buf_set_extmark(0, ns, 8, 0, {
      virt_lines = { { { 'Grugg' } } },
      virt_lines_above = true,
    })

    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      ^}                                                 |
      Grugg                                             |
                                                        |
    ]],
    }

    feed('dd')
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        ^}                                               |
      Grugg                                             |
      {1:~                                                 }|
                                                        |
    ]],
    }

    feed('dk')
    screen:expect {
      grid = [[
      if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          ^char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
      Grugg                                             |
      {1:~                                                 }|*3
                                                        |
    ]],
    }

    feed('dgg')
    screen:expect {
      grid = [[
      ^                                                  |
      Grugg                                             |
      {1:~                                                 }|*9
      --No lines in buffer--                            |
    ]],
    }

    api.nvim_buf_del_extmark(0, ns, id)
    screen:expect {
      grid = [[
      ^                                                  |
      {1:~                                                 }|*10
      --No lines in buffer--                            |
    ]],
    }
  end)

  it('does not cause syntax ml_get error at the end of a buffer #17816', function()
    command([[syntax region foo keepend start='^foo' end='^$']])
    command('syntax sync minlines=100')
    insert('foo')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'bar', 'Comment' } } } })
    screen:expect([[
      fo^o                                               |
      {18:bar}                                               |
      {1:~                                                 }|*9
                                                        |
    ]])
  end)

  it('works with a block scrolling up', function()
    screen:try_resize(30, 7)
    insert('aa\nbb\ncc\ndd\nee\nff\ngg\nhh')
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 6, 0, {
      virt_lines = {
        { { 'they see me' } },
        { { 'scrolling', 'Special' } },
        { { 'they' } },
        { { "hatin'", 'Special' } },
      },
    })

    screen:expect {
      grid = [[
      ^aa                            |
      bb                            |
      cc                            |
      dd                            |
      ee                            |
      ff                            |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^bb                            |
      cc                            |
      dd                            |
      ee                            |
      ff                            |
      gg                            |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^cc                            |
      dd                            |
      ee                            |
      ff                            |
      gg                            |
      they see me                   |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^dd                            |
      ee                            |
      ff                            |
      gg                            |
      they see me                   |
      {16:scrolling}                     |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^ee                            |
      ff                            |
      gg                            |
      they see me                   |
      {16:scrolling}                     |
      they                          |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^ff                            |
      gg                            |
      they see me                   |
      {16:scrolling}                     |
      they                          |
      {16:hatin'}                        |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^gg                            |
      they see me                   |
      {16:scrolling}                     |
      they                          |
      {16:hatin'}                        |
      hh                            |
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      they see me                   |
      {16:scrolling}                     |
      they                          |
      {16:hatin'}                        |
      ^hh                            |
      {1:~                             }|
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      {16:scrolling}                     |
      they                          |
      {16:hatin'}                        |
      ^hh                            |
      {1:~                             }|*2
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      they                          |
      {16:hatin'}                        |
      ^hh                            |
      {1:~                             }|*3
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      {16:hatin'}                        |
      ^hh                            |
      {1:~                             }|*4
                                    |
    ]],
    }

    feed '<c-e>'
    screen:expect {
      grid = [[
      ^hh                            |
      {1:~                             }|*5
                                    |
    ]],
    }
  end)

  it('works with sign and numbercolumns', function()
    insert(example_text2)
    feed 'gg'
    command 'set number signcolumn=yes'
    screen:expect([[
      {7:  }{8:  1 }^if (h->n_buckets < new_n_buckets) { // expan|
      {7:  }{8:    }d                                           |
      {7:  }{8:  2 }  khkey_t *new_keys = (khkey_t *)krealloc((v|
      {7:  }{8:    }oid *)h->keys, new_n_buckets * sizeof(khkey_|
      {7:  }{8:    }t));                                        |
      {7:  }{8:  3 }  h->keys = new_keys;                       |
      {7:  }{8:  4 }  if (kh_is_map && val_size) {              |
      {7:  }{8:  5 }    char *new_vals = krealloc( h->vals_buf, |
      {7:  }{8:    }new_n_buckets * val_size);                  |
      {7:  }{8:  6 }    h->vals_buf = new_vals;                 |
      {7:  }{8:  7 }  }                                         |
                                                        |
    ]])

    local markid = api.nvim_buf_set_extmark(0, ns, 2, 0, {
      virt_lines = {
        { { 'Some special', 'Special' } },
        { { 'remark about codes', 'Comment' } },
      },
    })

    screen:expect([[
      {7:  }{8:  1 }^if (h->n_buckets < new_n_buckets) { // expan|
      {7:  }{8:    }d                                           |
      {7:  }{8:  2 }  khkey_t *new_keys = (khkey_t *)krealloc((v|
      {7:  }{8:    }oid *)h->keys, new_n_buckets * sizeof(khkey_|
      {7:  }{8:    }t));                                        |
      {7:  }{8:  3 }  h->keys = new_keys;                       |
      {7:  }{8:    }{16:Some special}                                |
      {7:  }{8:    }{18:remark about codes}                          |
      {7:  }{8:  4 }  if (kh_is_map && val_size) {              |
      {7:  }{8:  5 }    char *new_vals = krealloc( h->vals_buf, |
      {7:  }{8:    }new_n_buckets * val_size);                  |
                                                        |
    ]])

    api.nvim_buf_set_extmark(0, ns, 2, 0, {
      virt_lines = {
        { { 'Some special', 'Special' } },
        { { 'remark about codes', 'Comment' } },
      },
      virt_lines_leftcol = true,
      id = markid,
    })
    screen:expect([[
      {7:  }{8:  1 }^if (h->n_buckets < new_n_buckets) { // expan|
      {7:  }{8:    }d                                           |
      {7:  }{8:  2 }  khkey_t *new_keys = (khkey_t *)krealloc((v|
      {7:  }{8:    }oid *)h->keys, new_n_buckets * sizeof(khkey_|
      {7:  }{8:    }t));                                        |
      {7:  }{8:  3 }  h->keys = new_keys;                       |
      {16:Some special}                                      |
      {18:remark about codes}                                |
      {7:  }{8:  4 }  if (kh_is_map && val_size) {              |
      {7:  }{8:  5 }    char *new_vals = krealloc( h->vals_buf, |
      {7:  }{8:    }new_n_buckets * val_size);                  |
                                                        |
    ]])

    command('set nonumber relativenumber')
    screen:expect([[
      {7:  }{8:  0 }^if (h->n_buckets < new_n_buckets) { // expan|
      {7:  }{8:    }d                                           |
      {7:  }{8:  1 }  khkey_t *new_keys = (khkey_t *)krealloc((v|
      {7:  }{8:    }oid *)h->keys, new_n_buckets * sizeof(khkey_|
      {7:  }{8:    }t));                                        |
      {7:  }{8:  2 }  h->keys = new_keys;                       |
      {16:Some special}                                      |
      {18:remark about codes}                                |
      {7:  }{8:  3 }  if (kh_is_map && val_size) {              |
      {7:  }{8:  4 }    char *new_vals = krealloc( h->vals_buf, |
      {7:  }{8:    }new_n_buckets * val_size);                  |
                                                        |
    ]])

    -- 'relativenumber' is redrawn with virt_lines_leftcol #34649
    feed('j')
    screen:expect([[
      {7:  }{8:  1 }if (h->n_buckets < new_n_buckets) { // expan|
      {7:  }{8:    }d                                           |
      {7:  }{8:  0 }^  khkey_t *new_keys = (khkey_t *)krealloc((v|
      {7:  }{8:    }oid *)h->keys, new_n_buckets * sizeof(khkey_|
      {7:  }{8:    }t));                                        |
      {7:  }{8:  1 }  h->keys = new_keys;                       |
      {16:Some special}                                      |
      {18:remark about codes}                                |
      {7:  }{8:  2 }  if (kh_is_map && val_size) {              |
      {7:  }{8:  3 }    char *new_vals = krealloc( h->vals_buf, |
      {7:  }{8:    }new_n_buckets * val_size);                  |
                                                        |
    ]])
  end)

  it('works with hard TABs', function()
    insert(example_text2)
    feed 'gg'
    api.nvim_buf_set_extmark(0, ns, 1, 0, {
      virt_lines = { { { '>>', 'NonText' }, { '\tvery\ttabby', 'Identifier' }, { 'text\twith\ttabs' } } },
    })
    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
      {1:>>}{25:      very    tabby}text       with    tabs      |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }

    command 'set tabstop=4'
    screen:expect {
      grid = [[
      ^if (h->n_buckets < new_n_buckets) { // expand     |
        khkey_t *new_keys = (khkey_t *)krealloc((void *)|
      h->keys, new_n_buckets * sizeof(khkey_t));        |
      {1:>>}{25:  very    tabby}text   with    tabs              |
        h->keys = new_keys;                             |
        if (kh_is_map && val_size) {                    |
          char *new_vals = krealloc( h->vals_buf, new_n_|
      buckets * val_size);                              |
          h->vals_buf = new_vals;                       |
        }                                               |
      }                                                 |
                                                        |
    ]],
    }

    command 'set number'
    screen:expect {
      grid = [[
      {8:  1 }^if (h->n_buckets < new_n_buckets) { // expand |
      {8:  2 }  khkey_t *new_keys = (khkey_t *)krealloc((voi|
      {8:    }d *)h->keys, new_n_buckets * sizeof(khkey_t));|
      {8:    }{1:>>}{25:  very    tabby}text   with    tabs          |
      {8:  3 }  h->keys = new_keys;                         |
      {8:  4 }  if (kh_is_map && val_size) {                |
      {8:  5 }    char *new_vals = krealloc( h->vals_buf, ne|
      {8:    }w_n_buckets * val_size);                      |
      {8:  6 }    h->vals_buf = new_vals;                   |
      {8:  7 }  }                                           |
      {8:  8 }}                                             |
                                                        |
    ]],
    }

    command 'set tabstop&'
    screen:expect {
      grid = [[
      {8:  1 }^if (h->n_buckets < new_n_buckets) { // expand |
      {8:  2 }  khkey_t *new_keys = (khkey_t *)krealloc((voi|
      {8:    }d *)h->keys, new_n_buckets * sizeof(khkey_t));|
      {8:    }{1:>>}{25:      very    tabby}text       with    tabs  |
      {8:  3 }  h->keys = new_keys;                         |
      {8:  4 }  if (kh_is_map && val_size) {                |
      {8:  5 }    char *new_vals = krealloc( h->vals_buf, ne|
      {8:    }w_n_buckets * val_size);                      |
      {8:  6 }    h->vals_buf = new_vals;                   |
      {8:  7 }  }                                           |
      {8:  8 }}                                             |
                                                        |
    ]],
    }
  end)

  it('scrolls horizontally with virt_lines_overflow = "scroll" #31000', function()
    command('set nowrap signcolumn=yes')
    insert('abcdefghijklmnopqrstuvwxyz')
    api.nvim_buf_set_extmark(0, ns, 0, 0, {
      virt_lines = {
        { { '12α口β̳γ̲=', 'Special' }, { '❤️345678', 'Special' } },
        { { '123\t45\t678', 'NonText' } },
      },
      virt_lines_overflow = 'scroll',
    })
    screen:expect([[
      {7:  }abcdefghijklmnopqrstuvwxy^z                      |
      {7:  }{16:12α口β̳γ̲=❤️345678}                                |
      {7:  }{1:123     45      678}                             |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }bcdefghijklmnopqrstuvwxy^z                       |
      {7:  }{16:2α口β̳γ̲=❤️345678}                                 |
      {7:  }{1:23     45      678}                              |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }cdefghijklmnopqrstuvwxy^z                        |
      {7:  }{16:α口β̳γ̲=❤️345678}                                  |
      {7:  }{1:3     45      678}                               |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }defghijklmnopqrstuvwxy^z                         |
      {7:  }{16:口β̳γ̲=❤️345678}                                   |
      {7:  }{1:     45      678}                                |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }efghijklmnopqrstuvwxy^z                          |
      {7:  }{16: β̳γ̲=❤️345678}                                    |
      {7:  }{1:    45      678}                                 |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }fghijklmnopqrstuvwxy^z                           |
      {7:  }{16:β̳γ̲=❤️345678}                                     |
      {7:  }{1:   45      678}                                  |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }ghijklmnopqrstuvwxy^z                            |
      {7:  }{16:γ̲=❤️345678}                                      |
      {7:  }{1:  45      678}                                   |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }hijklmnopqrstuvwxy^z                             |
      {7:  }{16:=❤️345678}                                       |
      {7:  }{1: 45      678}                                    |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }ijklmnopqrstuvwxy^z                              |
      {7:  }{16:❤️345678}                                        |
      {7:  }{1:45      678}                                     |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }jklmnopqrstuvwxy^z                               |
      {7:  }{16: 345678}                                         |
      {7:  }{1:5      678}                                      |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }klmnopqrstuvwxy^z                                |
      {7:  }{16:345678}                                          |
      {7:  }{1:      678}                                       |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }lmnopqrstuvwxy^z                                 |
      {7:  }{16:45678}                                           |
      {7:  }{1:     678}                                        |
      {1:~                                                 }|*8
                                                        |
    ]])
    feed('zl')
    screen:expect([[
      {7:  }mnopqrstuvwxy^z                                  |
      {7:  }{16:5678}                                            |
      {7:  }{1:    678}                                         |
      {1:~                                                 }|*8
                                                        |
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 1, {
      virt_lines = { { { '123\t45\t67', 'NonText' } } },
      virt_lines_leftcol = true,
      virt_lines_overflow = 'trunc',
    })
    api.nvim_buf_set_extmark(0, ns, 0, 2, {
      virt_lines = { { { '123\t45\t6', 'NonText' } } },
      virt_lines_leftcol = false,
      virt_lines_overflow = 'trunc',
    })
    screen:expect([[
      {7:  }mnopqrstuvwxy^z                                  |
      {7:  }{16:5678}                                            |
      {7:  }{1:    678}                                         |
      {1:123     45      67}                                |
      {7:  }{1:123     45      6}                               |
      {1:~                                                 }|*6
                                                        |
    ]])
  end)

  it('does not show twice if end_row or end_col is specified #18622', function()
    screen:try_resize(50, 8)
    insert([[
      aaa
      bbb
      ccc
      ddd]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 2, virt_lines = { { { 'VIRT LINE 1', 'NonText' } } } })
    api.nvim_buf_set_extmark(0, ns, 3, 0, { end_col = 2, virt_lines = { { { 'VIRT LINE 2', 'NonText' } } } })
    screen:expect {
      grid = [[
      aaa                                               |
      {1:VIRT LINE 1}                                       |
      bbb                                               |
      ccc                                               |
      dd^d                                               |
      {1:VIRT LINE 2}                                       |
      {1:~                                                 }|
                                                        |
    ]],
    }
  end)

  it('works with rightleft', function()
    screen:try_resize(50, 8)
    insert([[
      aaa
      bbb
      ccc
      ddd]])
    command('set number rightleft')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'VIRT LINE 1', 'NonText' } } }, virt_lines_leftcol = true })
    api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { 'VIRT LINE 2', 'NonText' } } } })
    screen:expect {
      grid = [[
                                                 aaa{8: 1  }|
                                             {1:1 ENIL TRIV}|
                                                 bbb{8: 2  }|
                                                 ccc{8: 3  }|
                                                 ^ddd{8: 4  }|
                                         {1:2 ENIL TRIV}{8:    }|
      {1:                                                 ~}|
                                                        |
    ]],
    }
  end)

  it('works when using dd or yyp #23915 #23916', function()
    insert([[
      line1
      line2
      line3
      line4
      line5]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'foo' } }, { { 'bar' } }, { { 'baz' } } } })
    screen:expect {
      grid = [[
      line1                                             |
      foo                                               |
      bar                                               |
      baz                                               |
      line2                                             |
      line3                                             |
      line4                                             |
      line^5                                             |
      {1:~                                                 }|*3
                                                        |
    ]],
    }

    feed('gg')
    feed('yyp')
    screen:expect {
      grid = [[
      line1                                             |
      foo                                               |
      bar                                               |
      baz                                               |
      ^line1                                             |
      line2                                             |
      line3                                             |
      line4                                             |
      line5                                             |
      {1:~                                                 }|*2
                                                        |
    ]],
    }

    feed('dd')
    screen:expect {
      grid = [[
      line1                                             |
      foo                                               |
      bar                                               |
      baz                                               |
      ^line2                                             |
      line3                                             |
      line4                                             |
      line5                                             |
      {1:~                                                 }|*3
                                                        |
    ]],
    }

    feed('kdd')
    screen:expect([[
      ^line2                                             |
      foo                                               |
      bar                                               |
      baz                                               |
      line3                                             |
      line4                                             |
      line5                                             |
      {1:~                                                 }|*4
                                                        |
    ]])
  end)

  it('does not break cursor position with concealcursor #27887', function()
    command('vsplit')
    insert('\n')
    api.nvim_set_option_value('conceallevel', 2, {})
    api.nvim_set_option_value('concealcursor', 'niv', {})
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'VIRT1' } }, { { 'VIRT2' } } } })
    screen:expect([[
                               │                        |
      VIRT1                    │VIRT1                   |
      VIRT2                    │VIRT2                   |
      ^                         │                        |
      {1:~                        }│{1:~                       }|*6
      {3:[No Name] [+]             }{2:[No Name] [+]           }|
                                                        |
    ]])
  end)

  it('works with full page scrolling #28290', function()
    screen:try_resize(20, 8)
    command('call setline(1, range(20))')
    api.nvim_buf_set_extmark(0, ns, 10, 0, { virt_lines = { { { 'VIRT1' } }, { { 'VIRT2' } } } })
    screen:expect([[
      ^0                   |
      1                   |
      2                   |
      3                   |
      4                   |
      5                   |
      6                   |
                          |
    ]])
    feed('<C-F>')
    screen:expect([[
      ^5                   |
      6                   |
      7                   |
      8                   |
      9                   |
      10                  |
      VIRT1               |
                          |
    ]])
    feed('<C-F>')
    screen:expect([[
      ^10                  |
      VIRT1               |
      VIRT2               |
      11                  |
      12                  |
      13                  |
      14                  |
                          |
    ]])
    feed('<C-F>')
    screen:expect([[
      ^13                  |
      14                  |
      15                  |
      16                  |
      17                  |
      18                  |
      19                  |
                          |
    ]])
    feed('<C-B>')
    screen:expect([[
      10                  |
      VIRT1               |
      VIRT2               |
      11                  |
      12                  |
      13                  |
      ^14                  |
                          |
    ]])
    feed('<C-B>')
    screen:expect([[
      5                   |
      6                   |
      7                   |
      8                   |
      9                   |
      ^10                  |
      VIRT1               |
                          |
    ]])
    feed('<C-B>')
    screen:expect([[
      0                   |
      1                   |
      2                   |
      3                   |
      4                   |
      5                   |
      ^6                   |
                          |
    ]])
  end)

  it('not drawn when invalid', function()
    api.nvim_buf_set_lines(0, 0, -1, false, { 'foo', 'bar' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = { { { 'VIRT1' } } }, invalidate = true })
    screen:expect({
      grid = [[
        ^foo                                               |
        VIRT1                                             |
        bar                                               |
        {1:~                                                 }|*8
                                                          |
      ]],
    })
    feed('dd')
    screen:expect({
      grid = [[
        ^bar                                               |
        {1:~                                                 }|*10
                                                          |
      ]],
    })
  end)

  it("not revealed before skipcol scrolling up with 'smoothscroll'", function()
    api.nvim_set_option_value('smoothscroll', true, {})
    api.nvim_buf_set_lines(0, 0, -1, false, { ('x'):rep(screen._width * 2) })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = { { { 'VIRT1' } } } })
    feed('<C-E>')
    screen:expect([[
      {1:<<<}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
      {1:~                                                 }|*10
                                                        |
    ]])
    feed('<C-Y>')
    screen:expect([[
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
      {1:~                                                 }|*9
                                                        |
    ]])
    feed('<C-Y>')
    screen:expect([[
      VIRT1                                             |
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
      {1:~                                                 }|*8
                                                        |
    ]])
  end)
end)

describe('decorations: signs', function()
  local screen ---@type test.functional.ui.screen
  local ns ---@type integer

  before_each(function()
    clear()
    screen = Screen.new(50, 10)
    screen:add_extra_attr_ids {
      [100] = { foreground = Screen.colors.Blue, background = Screen.colors.Yellow },
    }

    ns = api.nvim_create_namespace 'test'
    api.nvim_set_option_value('signcolumn', 'auto:9', {})
  end)

  local example_test3 = [[
l1
l2
l3
l4
l5
]]

  it('can add a single sign (no end row)', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S' })

    screen:expect([[
      {7:  }^l1                                              |
      {7:S }l2                                              |
      {7:  }l3                                              |
      {7:  }l4                                              |
      {7:  }l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add a single sign (with end row)', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S', end_row = 1 })

    screen:expect([[
      {7:  }^l1                                              |
      {7:S }l2                                              |
      {7:  }l3                                              |
      {7:  }l4                                              |
      {7:  }l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add a single sign and text highlight', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, 0, { sign_text = 'S', hl_group = 'Todo', end_col = 1 })
    screen:expect([[
      {7:  }^l1                                              |
      {7:S }{100:l}2                                              |
      {7:  }l3                                              |
      {7:  }l4                                              |
      {7:  }l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
  end)

  it('can add multiple signs (single extmark)', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S', end_row = 2 })

    screen:expect([[
      {7:  }^l1                                              |
      {7:S }l2                                              |
      {7:S }l3                                              |
      {7:  }l4                                              |
      {7:  }l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add multiple signs (multiple extmarks)', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 3, -1, { sign_text = 'S2', end_row = 4 })

    screen:expect([[
      {7:  }^l1                                              |
      {7:S1}l2                                              |
      {7:  }l3                                              |
      {7:S2}l4                                              |
      {7:S2}l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add multiple signs (multiple extmarks) 2', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 3, -1, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S2', end_row = 3 })
    screen:expect([[
      {7:    }^l1                                            |
      {7:S2  }l2                                            |
      {7:S2  }l3                                            |
      {7:S2S1}l4                                            |
      {7:    }l5                                            |
      {7:    }                                              |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add multiple signs (multiple extmarks) 3', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S1', end_row = 2 })
    api.nvim_buf_set_extmark(0, ns, 2, -1, { sign_text = 'S2', end_row = 3 })

    screen:expect([[
      {7:    }^l1                                            |
      {7:S1  }l2                                            |
      {7:S2S1}l3                                            |
      {7:S2  }l4                                            |
      {7:    }l5                                            |
      {7:    }                                              |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add multiple signs (multiple extmarks) 4', function()
    insert(example_test3)
    feed 'gg'

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1', end_row = 0 })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S2', end_row = 1 })

    screen:expect([[
      {7:S1}^l1                                              |
      {7:S2}l2                                              |
      {7:  }l3                                              |
      {7:  }l4                                              |
      {7:  }l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('works with old signs', function()
    insert(example_test3)
    feed 'gg'

    n.command('sign define Oldsign text=x')
    n.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]])

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S2' })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S4' })
    api.nvim_buf_set_extmark(0, ns, 2, -1, { sign_text = 'S5' })

    screen:expect([[
      {7:S4S1}^l1                                            |
      {7:S2x }l2                                            |
      {7:S5  }l3                                            |
      {7:    }l4                                            |
      {7:    }l5                                            |
      {7:    }                                              |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('works with old signs (with range)', function()
    insert(example_test3)
    feed 'gg'

    n.command('sign define Oldsign text=x')
    n.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]])

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S2' })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S3', end_row = 4 })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S4' })
    api.nvim_buf_set_extmark(0, ns, 2, -1, { sign_text = 'S5' })

    screen:expect([[
      {7:S4S3S1}^l1                                          |
      {7:S3S2x }l2                                          |
      {7:S5S3  }l3                                          |
      {7:S3    }l4                                          |
      {7:S3    }l5                                          |
      {7:      }                                            |
      {1:~                                                 }|*3
                                                        |
    ]])
  end)

  it('can add a ranged sign (with start out of view)', function()
    insert(example_test3)
    command 'set signcolumn=yes:2'
    feed 'gg'
    feed '2<C-e>'

    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'X', end_row = 3 })

    screen:expect([[
      {7:X   }^l3                                            |
      {7:X   }l4                                            |
      {7:    }l5                                            |
      {7:    }                                              |
      {1:~                                                 }|*5
                                                        |
    ]])
  end)

  it('can add lots of signs', function()
    screen:try_resize(40, 10)
    command 'normal 10oa b c d e f g h'

    for i = 1, 10 do
      api.nvim_buf_set_extmark(0, ns, i, 0, { end_col = 1, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 2, { end_col = 3, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 4, { end_col = 5, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 6, { end_col = 7, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 8, { end_col = 9, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 10, { end_col = 11, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 12, { end_col = 13, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, 14, { end_col = 15, hl_group = 'Todo' })
      api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text = 'W' })
      api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text = 'X' })
      api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text = 'Y' })
      api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text = 'Z' })
    end

    screen:expect([[
      {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h}                 |*8
      {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h}                 |
                                              |
    ]])
  end)

  it('works with priority #19716', function()
    screen:try_resize(20, 3)
    insert(example_test3)
    feed 'gg'

    command('sign define Oldsign text=O3')
    command([[exe 'sign place 42 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S4', priority = 100 })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S2', priority = 5 })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S5', priority = 200 })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1', priority = 1 })

    screen:expect([[
      {7:S5S4O3S2S1}^l1        |
      {7:          }l2        |
                          |
    ]])

    -- Check truncation works too
    api.nvim_set_option_value('signcolumn', 'auto', {})

    screen:expect([[
      {7:S5}^l1                |
      {7:  }l2                |
                          |
    ]])
  end)

  it('does not overflow with many old signs #23852', function()
    screen:try_resize(20, 3)

    command('set signcolumn:auto:9')
    command('sign define Oldsign text=O3')
    command([[exe 'sign place 01 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 02 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 03 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 04 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 05 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 06 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 07 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 08 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    command([[exe 'sign place 09 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
    screen:expect([[
      {7:O3O3O3O3O3O3O3O3O3}^  |
      {1:~                   }|
                          |
    ]])

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1', priority = 1 })
    screen:expect_unchanged()

    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S5', priority = 200 })
    screen:expect([[
      {7:S5O3O3O3O3O3O3O3O3}^  |
      {1:~                   }|
                          |
    ]])

    assert_alive()
  end)

  it('does not set signcolumn for signs without text', function()
    screen:try_resize(20, 3)
    api.nvim_set_option_value('signcolumn', 'auto', {})
    insert(example_test3)
    feed 'gg'
    api.nvim_buf_set_extmark(0, ns, 0, -1, { number_hl_group = 'Error' })
    screen:expect { grid = [[
      ^l1                  |
      l2                  |
                          |
    ]] }
  end)

  it('correct width when removing multiple signs from sentinel line', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1', end_row = 3 })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { invalidate = true, sign_text = 'S2' })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { invalidate = true, sign_text = 'S3' })
    feed('2Gdd')

    screen:expect([[
      {7:S1}l1                |
      {7:S1}^l3                |
      {7:S1}l4                |
                          |
    ]])
  end)

  it('correct width with multiple overlapping signs', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 0, -1, { sign_text = 'S2', end_row = 2 })
    api.nvim_buf_set_extmark(0, ns, 1, -1, { sign_text = 'S3', end_row = 2 })
    feed('gg')

    local s1 = [[
      {7:S2S1}^l1              |
      {7:S3S2}l2              |
      {7:S3S2}l3              |
                          |
    ]]
    screen:expect(s1)
    -- Correct width when :move'ing a line with signs
    command('move2')
    screen:expect([[
      {7:S3    }l2            |
      {7:S3S2S1}^l1            |
      {7:      }l3            |
                          |
    ]])
    command('silent undo')
    screen:expect { grid = s1 }
    command('d')
    screen:expect([[
      {7:S3S2S1}^l2            |
      {7:S3S2  }l3            |
      {7:      }l4            |
                          |
    ]])
    command('d')
    screen:expect([[
      {7:S3S2S1}^l3            |
      {7:      }l4            |
      {7:      }l5            |
                          |
    ]])
  end)

  it('correct width when adding and removing multiple signs', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    feed('gg')
    command([[
      let ns = nvim_create_namespace('')
      call nvim_buf_set_extmark(0, ns, 0, 0, {'sign_text':'S1', 'end_row':3})
      let s1 = nvim_buf_set_extmark(0, ns, 2, 0, {'sign_text':'S2', 'end_row':4})
      let s2 = nvim_buf_set_extmark(0, ns, 5, 0, {'sign_text':'S3'})
      let s3 = nvim_buf_set_extmark(0, ns, 6, 0, {'sign_text':'S3'})
      let s4 = nvim_buf_set_extmark(0, ns, 5, 0, {'sign_text':'S3'})
      let s5 = nvim_buf_set_extmark(0, ns, 6, 0, {'sign_text':'S3'})
      redraw!
      call nvim_buf_del_extmark(0, ns, s2)
      call nvim_buf_del_extmark(0, ns, s3)
      call nvim_buf_del_extmark(0, ns, s4)
      call nvim_buf_del_extmark(0, ns, s5)
      redraw!
      call nvim_buf_del_extmark(0, ns, s1)
    ]])
    screen:expect([[
      {7:S1}^l1                |
      {7:S1}l2                |
      {7:S1}l3                |
                          |
    ]])
  end)

  it('correct width when deleting lines', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    feed('gg')
    command([[
      let ns = nvim_create_namespace('')
      call nvim_buf_set_extmark(0, ns, 4, 0, {'sign_text':'S1'})
      call nvim_buf_set_extmark(0, ns, 4, 0, {'sign_text':'S2'})
      let s3 =  nvim_buf_set_extmark(0, ns, 5, 0, {'sign_text':'S3'})
      call nvim_buf_del_extmark(0, ns, s3)
      norm 4Gdd
    ]])
    screen:expect([[
      {7:    }l3              |
      {7:S2S1}l5              |
      {7:    }^                |
                          |
    ]])
  end)

  it('correct width when splitting lines with signs on different columns', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    feed('gg')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 0, 1, { sign_text = 'S2' })
    feed('a<cr><esc>')
    screen:expect([[
      {7:S1}l                 |
      {7:S2}^1                 |
      {7:  }l2                |
                          |
    ]])
  end)

  it('correct width after wiping a buffer', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    feed('gg')
    local buf = api.nvim_get_current_buf()
    api.nvim_buf_set_extmark(buf, ns, 0, 0, { sign_text = 'h' })
    screen:expect([[
      {7:h }^l1                |
      {7:  }l2                |
      {7:  }l3                |
                          |
    ]])
    api.nvim_win_set_buf(0, api.nvim_create_buf(false, true))
    api.nvim_buf_delete(buf, { unload = true, force = true })
    api.nvim_buf_set_lines(buf, 0, -1, false, { '' })
    api.nvim_win_set_buf(0, buf)
    screen:expect { grid = [[
      ^                    |
      {1:~                   }|*2
                          |
    ]] }
  end)

  it('correct width with moved marks before undo savepos', function()
    screen:try_resize(20, 4)
    insert(example_test3)
    feed('gg')
    exec_lua([[
      local ns = vim.api.nvim_create_namespace('')
      vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' })
      vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { sign_text = 'S2' })
      local s3 = vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { sign_text = 'S3' })
      local s4 = vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { sign_text = 'S4' })
      vim.schedule(function()
        vim.cmd('silent d3')
        vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { id = s3, sign_text = 'S3' })
        vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { id = s4, sign_text = 'S4' })
        vim.cmd('silent undo')
        vim.api.nvim_buf_del_extmark(0, ns, s3)
      end)
    ]])

    screen:expect([[
      {7:S1}^l1                |
      {7:S2}l2                |
      {7:S4}l3                |
                          |
    ]])
  end)

  it('no crash with sign after many marks #27137', function()
    screen:try_resize(20, 4)
    insert('a')
    for _ = 0, 104 do
      api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'Error', end_col = 1 })
    end
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' })

    screen:expect([[
      {7:S1}{9:^a}                 |
      {1:~                   }|*2
                          |
    ]])
  end)

  it('correct sort order with multiple namespaces and same id', function()
    local ns2 = api.nvim_create_namespace('')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1', id = 1 })
    api.nvim_buf_set_extmark(0, ns2, 0, 0, { sign_text = 'S2', id = 1 })

    screen:expect([[
      {7:S2S1}^                                              |
      {1:~                                                 }|*8
                                                        |
    ]])
  end)

  it('correct number of signs after deleting text (#27046)', function()
    command('call setline(1, ["foo"]->repeat(31))')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 0, sign_text = 'S1' })
    api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 0, end_col = 3, hl_group = 'Error' })
    api.nvim_buf_set_extmark(0, ns, 9, 0, { end_row = 9, sign_text = 'S2' })
    api.nvim_buf_set_extmark(0, ns, 9, 0, { end_row = 9, end_col = 3, hl_group = 'Error' })
    api.nvim_buf_set_extmark(0, ns, 19, 0, { end_row = 19, sign_text = 'S3' })
    api.nvim_buf_set_extmark(0, ns, 19, 0, { end_row = 19, end_col = 3, hl_group = 'Error' })
    api.nvim_buf_set_extmark(0, ns, 29, 0, { end_row = 29, sign_text = 'S4' })
    api.nvim_buf_set_extmark(0, ns, 29, 0, { end_row = 29, end_col = 3, hl_group = 'Error' })
    api.nvim_buf_set_extmark(0, ns, 30, 0, { end_row = 30, sign_text = 'S5' })
    api.nvim_buf_set_extmark(0, ns, 30, 0, { end_row = 30, end_col = 3, hl_group = 'Error' })
    command('0d29')

    screen:expect([[
      {7:S4S3S2S1}{9:^foo}                                       |
      {7:S5      }{9:foo}                                       |
      {1:~                                                 }|*7
      29 fewer lines                                    |
    ]])

    api.nvim_buf_clear_namespace(0, ns, 0, -1)
  end)

  it([[correct numberwidth with 'signcolumn' set to "number" #28984]], function()
    command('set number numberwidth=1 signcolumn=number')
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' })
    screen:expect([[
      {7:S1 }^                                               |
      {1:~                                                 }|*8
                                                        |
    ]])
    api.nvim_buf_del_extmark(0, ns, 1)
    screen:expect([[
      {8:1 }^                                                |
      {1:~                                                 }|*8
                                                        |
    ]])
  end)

  it('supports emoji as signs', function()
    insert(example_test3)
    feed 'gg'
    api.nvim_buf_set_extmark(0, ns, 1, 0, { sign_text = '🧑‍🌾' })
    -- VS16 can change width of character
    api.nvim_buf_set_extmark(0, ns, 2, 0, { sign_text = '❤️' })
    api.nvim_buf_set_extmark(0, ns, 3, 0, { sign_text = '❤' })
    api.nvim_buf_set_extmark(0, ns, 4, 0, { sign_text = '❤x' })
    screen:expect([[
      {7:  }^l1                                              |
      {7:🧑‍🌾}l2                                              |
      {7:❤️}l3                                              |
      {7:❤ }l4                                              |
      {7:❤x}l5                                              |
      {7:  }                                                |
      {1:~                                                 }|*3
                                                        |
    ]])
    eq("Invalid 'sign_text'", pcall_err(api.nvim_buf_set_extmark, 0, ns, 5, 0, { sign_text = '❤️x' }))
  end)

  it('auto signcolumn hides with invalidated sign', function()
    api.nvim_set_option_value('signcolumn', 'auto', {})
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1', invalidate = true })
    feed('ia<cr>b<esc>dd')
    screen:expect({
      grid = [[
        ^a                                                 |
        {1:~                                                 }|*8
                                                          |
      ]],
    })
  end)

  it('signcolumn correctly tracked with signs beyond eob and pair end before start', function()
    api.nvim_set_option_value('signcolumn', 'auto:2', {})
    api.nvim_set_option_value('filetype', 'lua', {})
    api.nvim_buf_set_lines(0, 0, -1, false, { 'foo', 'bar' })
    api.nvim_buf_set_extmark(0, ns, 2, 0, { sign_text = 'S1' })
    api.nvim_set_hl(0, 'SignColumn', { link = 'Error' })
    screen:expect([[
      ^foo                                               |
      bar                                               |
      {1:~                                                 }|*7
                                                        |
    ]])
    api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S2', end_row = 1 })
    api.nvim_buf_set_lines(0, 0, -1, false, { '-- foo', '-- bar' })
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    screen:expect([[
      ^-- foo                                            |
      -- bar                                            |
      {1:~                                                 }|*7
                                                        |
    ]])
    assert_alive()
  end)
end)

describe('decorations: virt_text', function()
  local ns, screen ---@type integer, test.functional.ui.screen

  before_each(function()
    clear()
    screen = Screen.new(50, 10)
    ns = api.nvim_create_namespace('test')
  end)

  it('avoids regression in #17638', function()
    command 'set number relativenumber'
    command 'normal 4ohello'
    command 'normal aVIRTUAL'

    api.nvim_buf_set_extmark(0, ns, 2, 0, {
      virt_text = { { 'hello', 'String' } },
      virt_text_win_col = 20,
    })

    screen:expect {
      grid = [[
      {8:  4 }                                              |
      {8:  3 }hello                                         |
      {8:  2 }hello               {26:hello}                     |
      {8:  1 }hello                                         |
      {8:5   }helloVIRTUA^L                                  |
      {1:~                                                 }|*4
                                                        |
    ]],
    }

    -- Trigger a screen update
    feed('k')

    screen:expect {
      grid = [[
      {8:  3 }                                              |
      {8:  2 }hello                                         |
      {8:  1 }hello               {26:hello}                     |
      {8:4   }hell^o                                         |
      {8:  1 }helloVIRTUAL                                  |
      {1:~                                                 }|*4
                                                        |
    ]],
    }
  end)

  it('redraws correctly when re-using extmark ids', function()
    command 'normal 5ohello'

    screen:expect {
      grid = [[
                                                        |
      hello                                             |*4
      hell^o                                             |
      {1:~                                                 }|*3
                                                        |
    ]],
    }

    for row = 1, 5 do
      api.nvim_buf_set_extmark(0, ns, row, 0, { id = 1, virt_text = { { 'world', 'Normal' } } })
    end

    screen:expect {
      grid = [[
                                                        |
      hello                                             |*4
      hell^o world                                       |
      {1:~                                                 }|*3
                                                        |
    ]],
    }
  end)

  it('redraws correctly when removing mark whose end ends up in front of start', function()
    command('normal 5ohello')
    api.nvim_buf_set_extmark(0, ns, 2, 0, { end_col = 1, virt_text = { { 'world', 'Normal' } } })
    screen:expect([[
                                                        |
      hello                                             |
      hello world                                       |
      hello                                             |*2
      hell^o                                             |
      {1:~                                                 }|*3
                                                        |
    ]])
    feed('3Gdd')
    api.nvim_buf_clear_namespace(0, ns, 0, -1)
    screen:expect([[
                                                        |
      hello                                             |
      ^hello                                             |
      hello                                             |*2
      {1:~                                                 }|*4
                                                        |
    ]])
  end)
end)

describe('decorations: window scoped', function()
  local screen ---@type test.functional.ui.screen
  local ns ---@type integer
  local win_other ---@type integer

  local url = 'https://example.com'
  before_each(function()
    clear()
    screen = Screen.new(20, 10)
    screen:add_extra_attr_ids {
      [100] = { special = Screen.colors.Red, undercurl = true },
      [101] = { url = 'https://example.com' },
    }

    ns = api.nvim_create_namespace 'test'

    insert('12345')

    win_other = api.nvim_open_win(0, false, {
      col = 0,
      row = 0,
      width = 20,
      height = 10,
      relative = 'win',
      style = 'minimal',
      hide = true,
    })
  end)

  local noextmarks = {
    grid = [[
      1234^5               |
      {1:~                   }|*8
                          |
    ]],
  }

  local function set_extmark(line, col, opts)
    return api.nvim_buf_set_extmark(0, ns, line, col, opts)
  end

  it('hl_group', function()
    set_extmark(0, 0, {
      hl_group = 'Comment',
      end_col = 3,
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      {18:123}4^5               |
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)
  end)

  it('virt_text', function()
    set_extmark(0, 0, {
      virt_text = { { 'a', 'Comment' } },
      virt_text_pos = 'eol',
    })
    set_extmark(0, 5, {
      virt_text = { { 'b', 'Comment' } },
      virt_text_pos = 'inline',
    })
    set_extmark(0, 1, {
      virt_text = { { 'c', 'Comment' } },
      virt_text_pos = 'overlay',
    })
    set_extmark(0, 1, {
      virt_text = { { 'd', 'Comment' } },
      virt_text_pos = 'right_align',
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      1{18:c}34^5{18:b} {18:a}           {18:d}|
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)

    api.nvim__ns_set(ns, { wins = {} })

    screen:expect {
      grid = [[
      1{18:c}34^5{18:b} {18:a}           {18:d}|
      {1:~                   }|*8
                          |
    ]],
    }
  end)

  it('virt_lines', function()
    set_extmark(0, 0, {
      virt_lines = { { { 'a', 'Comment' } } },
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      1234^5               |
      {18:a}                   |
      {1:~                   }|*7
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)
  end)

  it('redraws correctly with inline virt_text and wrapping', function()
    set_extmark(0, 2, {
      virt_text = { { ('b'):rep(18), 'Comment' } },
      virt_text_pos = 'inline',
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      12{18:bbbbbbbbbbbbbbbbbb}|
      34^5                 |
      {1:~                   }|*7
                          |
    ]],
    }

    api.nvim__ns_set(ns, { wins = { win_other } })

    screen:expect(noextmarks)
  end)

  pending('sign_text', function()
    -- TODO(altermo): The window signcolumn width is calculated wrongly (when `signcolumn=auto`)
    -- This happens in function `win_redraw_signcols` on line containing `buf_meta_total(buf, kMTMetaSignText) > 0`
    set_extmark(0, 0, {
      sign_text = 'a',
      sign_hl_group = 'Comment',
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      a 1234^5             |
      {2:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)
  end)

  it('statuscolumn hl group', function()
    set_extmark(0, 0, {
      number_hl_group = 'comment',
    })
    set_extmark(0, 0, {
      line_hl_group = 'comment',
    })

    command 'set number'

    api.nvim__ns_set(ns, { wins = { win_other } })

    screen:expect {
      grid = [[
      {8:  1 }1234^5           |
      {1:~                   }|*8
                          |
    ]],
    }

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      {18:  1 1234^5           }|
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect {
      grid = [[
      {8:  1 }1234^5           |
      {1:~                   }|*8
                          |
    ]],
    }
  end)

  it('spell', function()
    api.nvim_buf_set_lines(0, 0, -1, true, { 'aa' })

    set_extmark(0, 0, {
      spell = true,
      end_col = 2,
    })

    command 'set spelloptions=noplainbuffer'
    command 'set spell'
    command 'syntax off'

    screen:expect({ unchanged = true })

    api.nvim__ns_set(ns, { wins = { win_other } })

    screen:expect {
      grid = [[
      a^a                  |
      {1:~                   }|*8
                          |
    ]],
    }

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      {100:a^a}                  |
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect {
      grid = [[
      a^a                  |
      {1:~                   }|*8
                          |
    ]],
    }
  end)

  it('url', function()
    set_extmark(0, 0, {
      end_col = 3,
      url = url,
    })

    api.nvim__ns_set(ns, { wins = { 0 } })

    screen:expect {
      grid = [[
      {101:123}4^5               |
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)
  end)

  it('change namespace scope', function()
    set_extmark(0, 0, {
      hl_group = 'Comment',
      end_col = 3,
    })

    api.nvim__ns_set(ns, { wins = { 0 } })
    eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns))

    screen:expect {
      grid = [[
      {18:123}4^5               |
      {1:~                   }|*8
                          |
    ]],
    }

    command 'split'
    command 'only'

    screen:expect(noextmarks)

    api.nvim__ns_set(ns, { wins = { 0 } })
    eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns))

    screen:expect {
      grid = [[
      {18:123}4^5               |
      {1:~                   }|*8
                          |
    ]],
    }

    local win_new = api.nvim_open_win(0, false, {
      col = 0,
      row = 0,
      width = 20,
      height = 10,
      relative = 'win',
      style = 'minimal',
      hide = true,
    })

    api.nvim__ns_set(ns, { wins = { win_new } })
    eq({ wins = { win_new } }, api.nvim__ns_get(ns))

    screen:expect(noextmarks)
  end)

  it('namespace get works', function()
    eq({ wins = {} }, api.nvim__ns_get(ns))

    api.nvim__ns_set(ns, { wins = { 0 } })

    eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns))

    api.nvim__ns_set(ns, { wins = {} })

    eq({ wins = {} }, api.nvim__ns_get(ns))
  end)

  it('remove window from namespace scope when deleted', function()
    api.nvim__ns_set(ns, { wins = { 0 } })

    eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns))

    command 'split'
    command 'only'

    eq({ wins = {} }, api.nvim__ns_get(ns))
  end)
end)
