HEX
Server: Apache/2.4.41 (Ubuntu)
System: Linux ip-172-31-42-149 5.15.0-1084-aws #91~20.04.1-Ubuntu SMP Fri May 2 07:00:04 UTC 2025 aarch64
User: ubuntu (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //home/ubuntu/neovim/test/functional/ui/hlstate_spec.lua
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local tt = require('test.functional.terminal.testutil')

local clear, insert = n.clear, n.insert
local command = n.command
local api = n.api
local testprg = n.testprg
local skip = t.skip
local is_os = t.is_os

describe('ext_hlstate detailed highlights', function()
  local screen

  before_each(function()
    clear()
    command('syntax on')
    command('hi VertSplit gui=reverse')
    screen = Screen.new(40, 8)
    screen:attach({ ext_hlstate = true })
  end)

  after_each(function()
    screen:detach()
  end)

  it('work with combined UI and syntax highlights', function()
    insert([[
      these are some lines
      with colorful text]])
    api.nvim_buf_add_highlight(0, -1, 'String', 0, 10, 14)
    api.nvim_buf_add_highlight(0, -1, 'Statement', 1, 5, -1)
    command('/th co')

    screen:expect(
      [[
      these are {1:some} lines                    |
      ^wi{2:th }{4:co}{3:lorful text}                      |
      {5:~                                       }|*5
      {8:search hit BOTTOM, continuing at TOP}{7:    }|
    ]],
      {
        [1] = {
          { foreground = Screen.colors.Magenta },
          { { hi_name = 'Constant', kind = 'syntax' } },
        },
        [2] = {
          { background = Screen.colors.Yellow },
          { { hi_name = 'Search', ui_name = 'Search', kind = 'ui' } },
        },
        [3] = {
          { bold = true, foreground = Screen.colors.Brown },
          { { hi_name = 'Statement', kind = 'syntax' } },
        },
        [4] = {
          { bold = true, background = Screen.colors.Yellow, foreground = Screen.colors.Brown },
          { 3, 2 },
        },
        [5] = {
          { bold = true, foreground = Screen.colors.Blue1 },
          { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
        },
        [6] = {
          { foreground = Screen.colors.Red },
          { { hi_name = 'WarningMsg', ui_name = 'WarningMsg', kind = 'ui' } },
        },
        [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
        [8] = { { foreground = Screen.colors.Red }, { 7, 6 } },
      }
    )
  end)

  it('work with cleared UI highlights', function()
    screen:set_default_attr_ids({
      [1] = { {}, { { hi_name = 'Normal', ui_name = 'WinSeparator', kind = 'ui' } } },
      [2] = {
        { bold = true, foreground = Screen.colors.Blue1 },
        { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
      },
      [3] = {
        { bold = true, reverse = true },
        { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } },
      },
      [4] = {
        { reverse = true },
        { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } },
      },
      [5] = { {}, { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } } },
      [6] = { {}, { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } } },
      [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
    })
    command('hi clear WinSeparator')
    command('vsplit')

    screen:expect([[
      ^                    {1:│}                   |
      {2:~                   }{1:│}{2:~                  }|*5
      {3:[No Name]            }{4:[No Name]          }|
      {7:                                        }|
    ]])

    command('hi clear StatusLine | hi clear StatuslineNC')
    screen:expect([[
      ^                    {1:│}                   |
      {2:~                   }{1:│}{2:~                  }|*5
      {5:[No Name]            }{6:[No Name]          }|
      {7:                                        }|
    ]])

    -- redrawing is done even if visible highlights didn't change
    command('wincmd w')
    screen:expect([[
                         {1:│}^                    |
      {2:~                  }{1:│}{2:~                   }|*5
      {6:[No Name]           }{5:[No Name]           }|
      {7:                                        }|
    ]])
  end)

  it('work with window-local highlights', function()
    screen:set_default_attr_ids({
      [1] = {
        { foreground = Screen.colors.Brown },
        { { hi_name = 'LineNr', ui_name = 'LineNr', kind = 'ui' } },
      },
      [2] = {
        { bold = true, foreground = Screen.colors.Blue1 },
        { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
      },
      [3] = {
        { bold = true, reverse = true },
        { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } },
      },
      [4] = {
        { reverse = true },
        { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } },
      },
      [5] = {
        { background = Screen.colors.Red, foreground = Screen.colors.Grey100 },
        { { hi_name = 'ErrorMsg', ui_name = 'LineNr', kind = 'ui' } },
      },
      [6] = {
        { bold = true, reverse = true },
        { { hi_name = 'Normal', ui_name = 'Normal', kind = 'ui' } },
      },
      [7] = { { foreground = Screen.colors.Brown, bold = true, reverse = true }, { 6, 1 } },
      [8] = { { foreground = Screen.colors.Blue1, bold = true, reverse = true }, { 6, 14 } },
      [9] = {
        { bold = true, foreground = Screen.colors.Brown },
        { { hi_name = 'NormalNC', ui_name = 'NormalNC', kind = 'ui' } },
      },
      [10] = { { bold = true, foreground = Screen.colors.Brown }, { 9, 1 } },
      [11] = { { bold = true, foreground = Screen.colors.Blue1 }, { 9, 14 } },
      [12] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
      [13] = {
        { background = Screen.colors.Red1, foreground = Screen.colors.Gray100 },
        { { ui_name = 'LineNr', kind = 'ui', hi_name = 'LineNr' } },
      },
      [14] = {
        { bold = true, foreground = Screen.colors.Blue },
        { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'EndOfBuffer' } },
      },
    })

    command('set number')
    command('split')
    -- NormalNC is not applied if not set, to avoid spurious redraws
    screen:expect([[
      {1:  1 }^                                    |
      {2:~                                       }|*2
      {3:[No Name]                               }|
      {1:  1 }                                    |
      {2:~                                       }|
      {4:[No Name]                               }|
      {12:                                        }|
    ]])

    command('set winhl=LineNr:ErrorMsg')
    screen:expect {
      grid = [[
      {13:  1 }^                                    |
      {14:~                                       }|*2
      {3:[No Name]                               }|
      {1:  1 }                                    |
      {2:~                                       }|
      {4:[No Name]                               }|
      {12:                                        }|
    ]],
    }

    command('set winhl=Normal:MsgSeparator,NormalNC:Statement')
    screen:expect([[
      {7:  1 }{6:^                                    }|
      {8:~                                       }|*2
      {3:[No Name]                               }|
      {1:  1 }                                    |
      {2:~                                       }|
      {4:[No Name]                               }|
      {12:                                        }|
    ]])

    command('wincmd w')
    screen:expect([[
      {10:  1 }{9:                                    }|
      {11:~                                       }|*2
      {4:[No Name]                               }|
      {1:  1 }^                                    |
      {2:~                                       }|
      {3:[No Name]                               }|
      {12:                                        }|
    ]])
  end)

  it('work with :terminal', function()
    skip(is_os('win'))

    screen:set_default_attr_ids({
      [1] = { {}, { { hi_name = 'TermCursorNC', ui_name = 'TermCursorNC', kind = 'ui' } } },
      [2] = { { foreground = tonumber('0x00ccff'), fg_indexed = true }, { { kind = 'term' } } },
      [3] = {
        { bold = true, foreground = tonumber('0x00ccff'), fg_indexed = true },
        {
          { kind = 'term' },
        },
      },
      [4] = { { foreground = tonumber('0x00ccff'), fg_indexed = true }, { 2, 1 } },
      [5] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { { kind = 'term' } } },
      [6] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { 5, 1 } },
      [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
    })
    command(("enew | call termopen(['%s'])"):format(testprg('tty-test')))
    screen:expect([[
      ^tty ready                               |
      {1: }                                       |
                                              |*5
      {7:                                        }|
    ]])

    tt.feed_data('x ')
    tt.set_fg(45)
    tt.feed_data('y ')
    tt.set_bold()
    tt.feed_data('z\n')
    -- TODO(bfredl): check if this distinction makes sense
    if is_os('win') then
      screen:expect([[
        ^tty ready                               |
        x {5:y z}                                   |
        {1: }                                       |
                                                |*4
        {7:                                        }|
      ]])
    else
      screen:expect([[
        ^tty ready                               |
        x {2:y }{3:z}                                   |
        {1: }                                       |
                                                |*4
        {7:                                        }|
      ]])
    end

    tt.feed_termcode('[A')
    tt.feed_termcode('[2C')
    if is_os('win') then
      screen:expect([[
        ^tty ready                               |
        x {6:y}{5: z}                                   |
                                                |*5
        {7:                                        }|
      ]])
    else
      screen:expect([[
        ^tty ready                               |
        x {4:y}{2: }{3:z}                                   |
                                                |*5
        {7:                                        }|
      ]])
    end
  end)

  it('can use independent cterm and rgb colors', function()
    -- tell test module to save all attributes (doesn't change nvim options)
    screen:set_rgb_cterm(true)

    screen:set_default_attr_ids({
      [1] = {
        { bold = true, foreground = Screen.colors.Blue1 },
        { foreground = 12 },
        { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
      },
      [2] = {
        { reverse = true, foreground = Screen.colors.Red },
        { foreground = 10, italic = true },
        { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
      },
      [3] = { {}, {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
    })
    screen:expect([[
      ^                                        |
      {1:~                                       }|*6
      {3:                                        }|
    ]])

    command('hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic')
    screen:expect([[
      ^                                        |
      {2:~                                       }|*6
      {3:                                        }|
    ]])
  end)

  it('combines deleted extmark highlights', function()
    insert([[
      line1
        line2
        line3
        line4
        line5
      line6]])

    screen:expect {
      grid = [[
      line1                                   |
        line2                                 |
        line3                                 |
        line4                                 |
        line5                                 |
      line^6                                   |
      {1:~                                       }|
      {2:                                        }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.Blue, bold = true },
          { { ui_name = 'EndOfBuffer', hi_name = 'NonText', kind = 'ui' } },
        },
        [2] = { {}, { { ui_name = 'MsgArea', hi_name = 'MsgArea', kind = 'ui' } } },
      },
    }

    local ns = api.nvim_create_namespace('test')

    local add_indicator = function(line, col)
      api.nvim_buf_set_extmark(0, ns, line, col, {
        hl_mode = 'combine',
        priority = 2,
        right_gravity = false,
        virt_text = { { '|', 'Delimiter' } },
        virt_text_win_col = 0,
        virt_text_pos = 'overlay',
      })
    end

    add_indicator(1, 0)
    add_indicator(2, 0)
    add_indicator(3, 0)
    add_indicator(4, 0)

    screen:expect {
      grid = [[
      line1                                   |
      {1:|} line2                                 |
      {1:|} line3                                 |
      {1:|} line4                                 |
      {1:|} line5                                 |
      line^6                                   |
      {2:~                                       }|
      {3:                                        }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { hi_name = 'Special', kind = 'syntax' } },
        },
        [2] = {
          { bold = true, foreground = Screen.colors.Blue },
          { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'NonText' } },
        },
        [3] = { {}, { { ui_name = 'MsgArea', kind = 'ui', hi_name = 'MsgArea' } } },
      },
    }

    n.feed('3ggV2jd')
    --screen:redraw_debug()
    screen:expect {
      grid = [[
      line1                                   |
      {1:|} line2                                 |
      {2:^|}ine6                                   |
      {3:~                                       }|*4
      {4:3 fewer lines                           }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { kind = 'syntax', hi_name = 'Special' } },
        },
        [2] = { { foreground = Screen.colors.SlateBlue }, { 1, 1, 1 } },
        [3] = {
          { bold = true, foreground = Screen.colors.Blue },
          { { kind = 'ui', ui_name = 'EndOfBuffer', hi_name = 'NonText' } },
        },
        [4] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
      },
    }
  end)

  it('removes deleted extmark highlights with invalidate', function()
    insert([[
      line1
        line2
        line3
        line4
        line5
      line6]])

    screen:expect {
      grid = [[
      line1                                   |
        line2                                 |
        line3                                 |
        line4                                 |
        line5                                 |
      line^6                                   |
      {1:~                                       }|
      {2:                                        }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.Blue, bold = true },
          { { ui_name = 'EndOfBuffer', hi_name = 'NonText', kind = 'ui' } },
        },
        [2] = { {}, { { ui_name = 'MsgArea', hi_name = 'MsgArea', kind = 'ui' } } },
      },
    }

    local ns = api.nvim_create_namespace('test')

    local add_indicator = function(line, col)
      api.nvim_buf_set_extmark(0, ns, line, col, {
        hl_mode = 'combine',
        priority = 2,
        right_gravity = false,
        virt_text = { { '|', 'Delimiter' } },
        virt_text_win_col = 0,
        virt_text_pos = 'overlay',
        invalidate = true,
      })
    end

    add_indicator(1, 0)
    add_indicator(2, 0)
    add_indicator(3, 0)
    add_indicator(4, 0)

    screen:expect {
      grid = [[
      line1                                   |
      {1:|} line2                                 |
      {1:|} line3                                 |
      {1:|} line4                                 |
      {1:|} line5                                 |
      line^6                                   |
      {2:~                                       }|
      {3:                                        }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { hi_name = 'Special', kind = 'syntax' } },
        },
        [2] = {
          { bold = true, foreground = Screen.colors.Blue },
          { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'NonText' } },
        },
        [3] = { {}, { { ui_name = 'MsgArea', kind = 'ui', hi_name = 'MsgArea' } } },
      },
    }

    n.feed('3ggV2jd')
    --screen:redraw_debug()
    screen:expect {
      grid = [[
      line1                                   |
      {1:|} line2                                 |
      ^line6                                   |
      {2:~                                       }|*4
      {3:3 fewer lines                           }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { kind = 'syntax', hi_name = 'Special' } },
        },
        [2] = {
          { foreground = Screen.colors.Blue, bold = true },
          { { kind = 'ui', ui_name = 'EndOfBuffer', hi_name = 'NonText' } },
        },
        [3] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
      },
    }
  end)

  it('does not hang when combining too many highlights', function()
    local num_lines = 500
    insert('first line\n')
    for _ = 1, num_lines do
      insert([[
        line
      ]])
    end
    insert('last line')

    n.feed('gg')
    screen:expect {
      grid = [[
      ^first line                              |
        line                                  |*6
      {1:                                        }|
    ]],
      attr_ids = {
        [1] = { {}, { { kind = 'ui', hi_name = 'MsgArea', ui_name = 'MsgArea' } } },
      },
    }
    local ns = api.nvim_create_namespace('test')

    local add_indicator = function(line, col)
      api.nvim_buf_set_extmark(0, ns, line, col, {
        hl_mode = 'combine',
        priority = 2,
        right_gravity = false,
        virt_text = { { '|', 'Delimiter' } },
        virt_text_win_col = 0,
        virt_text_pos = 'overlay',
      })
    end

    for i = 1, num_lines do
      add_indicator(i, 0)
    end

    screen:expect {
      grid = [[
      ^first line                              |
      {1:|} line                                  |*6
      {2:                                        }|
    ]],
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { kind = 'syntax', hi_name = 'Special' } },
        },
        [2] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
      },
    }

    n.feed(string.format('3ggV%ijd', num_lines - 2))
    --screen:redraw_debug(nil, nil, 100000)

    local expected_ids = {}
    for i = 1, num_lines - 1 do
      expected_ids[i] = 1
    end
    screen:expect {
      grid = string.format(
        [[
        first line                              |
        {1:|} line                                  |
        {2:^|}ast line                               |
        {3:~                                       }|*4
        {4:%-40s}|
    ]],
        tostring(num_lines - 1) .. ' fewer lines'
      ),
      attr_ids = {
        [1] = {
          { foreground = Screen.colors.SlateBlue },
          { { kind = 'syntax', hi_name = 'Special' } },
        },
        [2] = { { foreground = Screen.colors.SlateBlue }, expected_ids },
        [3] = {
          { foreground = Screen.colors.Blue, bold = true },
          { { kind = 'ui', hi_name = 'NonText', ui_name = 'EndOfBuffer' } },
        },
        [4] = { {}, { { kind = 'ui', hi_name = 'MsgArea', ui_name = 'MsgArea' } } },
      },
      timeout = 100000,
    }
  end)
end)