2017-01-18 12:28:35 -07:00
|
|
|
|
-- This module contains the Screen class, a complete Nvim UI implementation
|
|
|
|
|
-- designed for functional testing (verifying screen state, in particular).
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- Screen:expect() takes a string representing the expected screen state and an
|
|
|
|
|
-- optional set of attribute identifiers for checking highlighted characters.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
|
|
|
|
-- Example usage:
|
|
|
|
|
--
|
|
|
|
|
-- local screen = Screen.new(25, 10)
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- -- Attach the screen to the current Nvim instance.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
-- screen:attach()
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- -- Enter insert-mode and type some text.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
-- feed('ihello screen')
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- -- Assert the expected screen state.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
-- screen:expect([[
|
|
|
|
|
-- hello screen |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- -- INSERT -- |
|
|
|
|
|
-- ]]) -- <- Last line is stripped
|
|
|
|
|
--
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- Since screen updates are received asynchronously, expect() actually specifies
|
|
|
|
|
-- the _eventual_ screen state.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- This is how expect() works:
|
|
|
|
|
-- * It starts the event loop with a timeout.
|
|
|
|
|
-- * Each time it receives an update it checks that against the expected state.
|
|
|
|
|
-- * If the expected state matches the current state, the event loop will be
|
|
|
|
|
-- stopped and expect() will return.
|
|
|
|
|
-- * If the timeout expires, the last match error will be reported and the
|
|
|
|
|
-- test will fail.
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- Continuing the above example, say we want to assert that "-- INSERT --" is
|
|
|
|
|
-- highlighted with the bold attribute. The expect() call should look like this:
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
2015-01-26 11:32:20 -07:00
|
|
|
|
-- NonText = Screen.colors.Blue
|
2014-12-08 18:31:45 -07:00
|
|
|
|
-- screen:expect([[
|
2015-05-19 07:11:32 -07:00
|
|
|
|
-- hello screen |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- ~ |
|
|
|
|
|
-- {b:-- INSERT --} |
|
2015-01-24 07:07:02 -07:00
|
|
|
|
-- ]], {b = {bold = true}}, {{bold = true, foreground = NonText}})
|
2014-12-08 18:31:45 -07:00
|
|
|
|
--
|
|
|
|
|
-- In this case "b" is a string associated with the set composed of one
|
|
|
|
|
-- attribute: bold. Note that since the {b:} markup is not a real part of the
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- screen, the delimiter "|" moved to the right. Also, the highlighting of the
|
|
|
|
|
-- NonText markers "~" is ignored in this test.
|
|
|
|
|
--
|
|
|
|
|
-- Tests will often share a group of attribute sets to expect(). Those can be
|
|
|
|
|
-- defined at the beginning of a test:
|
2015-01-24 07:07:02 -07:00
|
|
|
|
--
|
2015-01-26 11:32:20 -07:00
|
|
|
|
-- NonText = Screen.colors.Blue
|
2015-01-24 07:07:02 -07:00
|
|
|
|
-- screen:set_default_attr_ids( {
|
|
|
|
|
-- [1] = {reverse = true, bold = true},
|
|
|
|
|
-- [2] = {reverse = true}
|
|
|
|
|
-- })
|
|
|
|
|
--
|
2017-01-19 17:20:34 -07:00
|
|
|
|
-- To help write screen tests, see Screen:snapshot_util().
|
|
|
|
|
-- To debug screen tests, see Screen:redraw_debug().
|
2015-01-24 07:07:02 -07:00
|
|
|
|
|
2016-04-23 16:53:11 -07:00
|
|
|
|
local helpers = require('test.functional.helpers')(nil)
|
2019-10-19 14:15:07 -07:00
|
|
|
|
local busted = require('busted')
|
2019-05-18 05:09:43 -07:00
|
|
|
|
local deepcopy = helpers.deepcopy
|
|
|
|
|
local shallowcopy = helpers.shallowcopy
|
|
|
|
|
local concat_tables = helpers.concat_tables
|
2023-02-17 18:27:10 -07:00
|
|
|
|
local pesc = helpers.pesc
|
2022-05-03 06:08:35 -07:00
|
|
|
|
local run_session = helpers.run_session
|
2018-07-24 02:54:09 -07:00
|
|
|
|
local eq = helpers.eq
|
2015-11-17 14:44:00 -07:00
|
|
|
|
local dedent = helpers.dedent
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local get_session = helpers.get_session
|
|
|
|
|
local create_callindex = helpers.create_callindex
|
2014-12-08 18:31:45 -07:00
|
|
|
|
|
2019-07-12 15:50:52 -07:00
|
|
|
|
local inspect = require('vim.inspect')
|
2018-08-31 06:56:53 -07:00
|
|
|
|
|
|
|
|
|
local function isempty(v)
|
|
|
|
|
return type(v) == 'table' and next(v) == nil
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local Screen = {}
|
|
|
|
|
Screen.__index = Screen
|
|
|
|
|
|
2017-06-26 05:49:15 -07:00
|
|
|
|
local default_timeout_factor = 1
|
2015-01-23 15:00:45 -07:00
|
|
|
|
if os.getenv('VALGRIND') then
|
2017-06-26 05:49:15 -07:00
|
|
|
|
default_timeout_factor = default_timeout_factor * 3
|
2015-02-13 06:28:02 -07:00
|
|
|
|
end
|
|
|
|
|
|
2016-01-01 21:58:42 -07:00
|
|
|
|
if os.getenv('CI') then
|
2017-06-26 05:49:15 -07:00
|
|
|
|
default_timeout_factor = default_timeout_factor * 3
|
2015-01-23 15:00:45 -07:00
|
|
|
|
end
|
2015-01-13 14:05:57 -07:00
|
|
|
|
|
2017-06-26 05:49:15 -07:00
|
|
|
|
local default_screen_timeout = default_timeout_factor * 3500
|
|
|
|
|
|
2022-11-27 02:33:54 -07:00
|
|
|
|
function Screen._init_colors(session)
|
2016-09-29 17:33:50 -07:00
|
|
|
|
local status, rv = session:request('nvim_get_color_map')
|
2015-03-17 04:45:13 -07:00
|
|
|
|
if not status then
|
2022-11-27 02:33:54 -07:00
|
|
|
|
error('failed to get color map')
|
2015-03-17 04:45:13 -07:00
|
|
|
|
end
|
|
|
|
|
local colors = rv
|
|
|
|
|
local colornames = {}
|
|
|
|
|
for name, rgb in pairs(colors) do
|
2015-01-26 11:32:20 -07:00
|
|
|
|
-- we disregard the case that colornames might not be unique, as
|
|
|
|
|
-- this is just a helper to get any canonical name of a color
|
|
|
|
|
colornames[rgb] = name
|
2015-03-17 04:45:13 -07:00
|
|
|
|
end
|
|
|
|
|
Screen.colors = colors
|
|
|
|
|
Screen.colornames = colornames
|
2015-01-26 11:32:20 -07:00
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
function Screen.new(width, height)
|
2022-11-27 02:33:54 -07:00
|
|
|
|
if not Screen.colors then
|
|
|
|
|
Screen._init_colors(get_session())
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
if not width then
|
|
|
|
|
width = 53
|
|
|
|
|
end
|
|
|
|
|
if not height then
|
|
|
|
|
height = 14
|
|
|
|
|
end
|
2015-01-15 05:01:25 -07:00
|
|
|
|
local self = setmetatable({
|
2015-10-01 11:03:40 -07:00
|
|
|
|
timeout = default_screen_timeout,
|
2015-01-15 05:01:25 -07:00
|
|
|
|
title = '',
|
|
|
|
|
icon = '',
|
|
|
|
|
bell = false,
|
2015-07-10 16:03:30 -07:00
|
|
|
|
update_menu = false,
|
2015-01-15 05:01:25 -07:00
|
|
|
|
visual_bell = false,
|
|
|
|
|
suspended = false,
|
2016-11-25 03:33:57 -07:00
|
|
|
|
mode = 'normal',
|
2017-12-12 10:23:19 -07:00
|
|
|
|
options = {},
|
2018-08-20 09:51:25 -07:00
|
|
|
|
popupmenu = nil,
|
|
|
|
|
cmdline = {},
|
|
|
|
|
cmdline_block = {},
|
|
|
|
|
wildmenu_items = nil,
|
|
|
|
|
wildmenu_selected = nil,
|
2018-11-18 02:00:27 -07:00
|
|
|
|
win_position = {},
|
2020-01-23 10:05:04 -07:00
|
|
|
|
win_viewport = {},
|
2017-04-26 06:28:10 -07:00
|
|
|
|
float_pos = {},
|
2019-09-01 02:25:00 -07:00
|
|
|
|
msg_grid = nil,
|
|
|
|
|
msg_grid_pos = nil,
|
2017-12-09 03:26:06 -07:00
|
|
|
|
_session = nil,
|
2023-10-09 01:14:37 -07:00
|
|
|
|
rpc_async = false,
|
2017-10-31 08:46:02 -07:00
|
|
|
|
messages = {},
|
|
|
|
|
msg_history = {},
|
|
|
|
|
showmode = {},
|
|
|
|
|
showcmd = {},
|
|
|
|
|
ruler = {},
|
2019-07-14 04:26:40 -07:00
|
|
|
|
hl_groups = {},
|
2014-12-08 18:31:45 -07:00
|
|
|
|
_default_attr_ids = nil,
|
2020-12-23 09:13:13 -07:00
|
|
|
|
mouse_enabled = true,
|
2014-12-08 18:31:45 -07:00
|
|
|
|
_attrs = {},
|
2019-10-11 10:27:15 -07:00
|
|
|
|
_hl_info = {[0]={}},
|
2018-07-06 05:39:50 -07:00
|
|
|
|
_attr_table = {[0]={{},{}}},
|
2019-10-11 10:27:15 -07:00
|
|
|
|
_clear_attrs = nil,
|
2018-06-17 04:31:13 -07:00
|
|
|
|
_new_attrs = false,
|
2017-06-26 05:49:15 -07:00
|
|
|
|
_width = width,
|
|
|
|
|
_height = height,
|
2017-12-09 03:26:06 -07:00
|
|
|
|
_grids = {},
|
2021-09-15 16:53:56 -07:00
|
|
|
|
_grid_win_extmarks = {},
|
2014-12-08 18:31:45 -07:00
|
|
|
|
_cursor = {
|
2017-12-09 03:26:06 -07:00
|
|
|
|
grid = 1, row = 1, col = 1
|
2015-03-15 06:21:05 -07:00
|
|
|
|
},
|
2017-12-09 03:26:06 -07:00
|
|
|
|
_busy = false,
|
2014-12-08 18:31:45 -07:00
|
|
|
|
}, Screen)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local function ui(method, ...)
|
2023-10-09 01:14:37 -07:00
|
|
|
|
if self.rpc_async then
|
|
|
|
|
self._session:notify('nvim_ui_'..method, ...)
|
|
|
|
|
else
|
|
|
|
|
local status, rv = self._session:request('nvim_ui_'..method, ...)
|
|
|
|
|
if not status then
|
|
|
|
|
error(rv[2])
|
|
|
|
|
end
|
2017-12-09 03:26:06 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
self.uimeths = create_callindex(ui)
|
2015-01-15 05:01:25 -07:00
|
|
|
|
return self
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:set_default_attr_ids(attr_ids)
|
|
|
|
|
self._default_attr_ids = attr_ids
|
2018-11-20 02:52:49 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:get_default_attr_ids()
|
|
|
|
|
return deepcopy(self._default_attr_ids)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2019-10-11 10:27:15 -07:00
|
|
|
|
function Screen:set_rgb_cterm(val)
|
|
|
|
|
self._rgb_cterm = val
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
function Screen:attach(options, session)
|
|
|
|
|
if session == nil then
|
|
|
|
|
session = get_session()
|
|
|
|
|
end
|
2016-06-08 02:26:06 -07:00
|
|
|
|
if options == nil then
|
2018-09-20 03:06:19 -07:00
|
|
|
|
options = {}
|
2015-03-25 05:14:47 -07:00
|
|
|
|
end
|
2018-09-28 05:19:37 -07:00
|
|
|
|
if options.ext_linegrid == nil then
|
|
|
|
|
options.ext_linegrid = true
|
2018-07-06 05:39:50 -07:00
|
|
|
|
end
|
2017-12-09 03:26:06 -07:00
|
|
|
|
|
|
|
|
|
self._session = session
|
2018-07-06 05:39:50 -07:00
|
|
|
|
self._options = options
|
2019-10-11 10:27:15 -07:00
|
|
|
|
self._clear_attrs = (not options.ext_linegrid) and {} or nil
|
2017-06-26 05:49:15 -07:00
|
|
|
|
self:_handle_resize(self._width, self._height)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self.uimeths.attach(self._width, self._height, options)
|
2018-09-20 03:06:19 -07:00
|
|
|
|
if self._options.rgb == nil then
|
|
|
|
|
-- nvim defaults to rgb=true internally,
|
|
|
|
|
-- simplify test code by doing the same.
|
|
|
|
|
self._options.rgb = true
|
|
|
|
|
end
|
2023-02-14 03:26:22 -07:00
|
|
|
|
if self._options.ext_multigrid then
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self._options.ext_linegrid = true
|
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:detach()
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self.uimeths.detach()
|
|
|
|
|
self._session = nil
|
2015-01-15 05:01:25 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:try_resize(columns, rows)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self._width = columns
|
|
|
|
|
self._height = rows
|
|
|
|
|
self.uimeths.try_resize(columns, rows)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:try_resize_grid(grid, columns, rows)
|
|
|
|
|
self.uimeths.try_resize_grid(grid, columns, rows)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-12-05 05:27:06 -07:00
|
|
|
|
function Screen:set_option(option, value)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self.uimeths.set_option(option, value)
|
2018-07-06 05:39:50 -07:00
|
|
|
|
self._options[option] = value
|
2017-12-05 05:27:06 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
-- canonical order of ext keys, used to generate asserts
|
|
|
|
|
local ext_keys = {
|
2017-10-31 08:46:02 -07:00
|
|
|
|
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos',
|
2021-01-20 02:25:52 -07:00
|
|
|
|
'messages', 'msg_history', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport'
|
2018-08-31 06:56:53 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-04 05:21:35 -07:00
|
|
|
|
-- Asserts that the screen state eventually matches an expected state.
|
2016-09-29 17:33:50 -07:00
|
|
|
|
--
|
2019-02-04 05:21:35 -07:00
|
|
|
|
-- Can be called with positional args:
|
2019-10-13 00:19:57 -07:00
|
|
|
|
-- screen:expect(grid, [attr_ids])
|
2019-02-04 05:21:35 -07:00
|
|
|
|
-- screen:expect(condition)
|
|
|
|
|
-- or keyword args (supports more options):
|
|
|
|
|
-- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end}
|
2018-08-20 09:51:25 -07:00
|
|
|
|
--
|
|
|
|
|
--
|
|
|
|
|
-- grid: Expected screen state (string). Each line represents a screen
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- row. Last character of each row (typically "|") is stripped.
|
|
|
|
|
-- Common indentation is stripped.
|
2023-02-17 18:27:10 -07:00
|
|
|
|
-- "{MATCH:x}" in a line is matched against Lua pattern `x`.
|
2017-01-18 12:28:35 -07:00
|
|
|
|
-- attr_ids: Expected text attributes. Screen rows are transformed according
|
|
|
|
|
-- to this table, as follows: each substring S composed of
|
|
|
|
|
-- characters having the same attributes will be substituted by
|
|
|
|
|
-- "{K:S}", where K is a key in `attr_ids`. Any unexpected
|
|
|
|
|
-- attributes in the final state are an error.
|
2018-08-20 09:51:25 -07:00
|
|
|
|
-- Use screen:set_default_attr_ids() to define attributes for many
|
|
|
|
|
-- expect() calls.
|
2021-09-15 16:53:56 -07:00
|
|
|
|
-- extmarks: Expected win_extmarks accumulated for the grids. For each grid,
|
|
|
|
|
-- the win_extmark messages are accumulated into an array.
|
2018-08-20 09:51:25 -07:00
|
|
|
|
-- condition: Function asserting some arbitrary condition. Return value is
|
|
|
|
|
-- ignored, throw an error (use eq() or similar) to signal failure.
|
2017-06-26 05:49:15 -07:00
|
|
|
|
-- any: Lua pattern string expected to match a screen line. NB: the
|
|
|
|
|
-- following chars are magic characters
|
|
|
|
|
-- ( ) . % + - * ? [ ^ $
|
|
|
|
|
-- and must be escaped with a preceding % for a literal match.
|
2018-08-20 09:51:25 -07:00
|
|
|
|
-- mode: Expected mode as signaled by "mode_change" event
|
2017-06-26 05:49:15 -07:00
|
|
|
|
-- unchanged: Test that the screen state is unchanged since the previous
|
|
|
|
|
-- expect(...). Any flush event resulting in a different state is
|
|
|
|
|
-- considered an error. Not observing any events until timeout
|
|
|
|
|
-- is acceptable.
|
|
|
|
|
-- intermediate:Test that the final state is the same as the previous expect,
|
|
|
|
|
-- but expect an intermediate state that is different. If possible
|
|
|
|
|
-- it is better to use an explicit screen:expect(...) for this
|
|
|
|
|
-- intermediate state.
|
|
|
|
|
-- reset: Reset the state internal to the test Screen before starting to
|
|
|
|
|
-- receive updates. This should be used after command("redraw!")
|
|
|
|
|
-- or some other mechanism that will invoke "redraw!", to check
|
|
|
|
|
-- that all screen state is transmitted again. This includes
|
|
|
|
|
-- state related to ext_ features as mentioned below.
|
|
|
|
|
-- timeout: maximum time that will be waited until the expected state is
|
|
|
|
|
-- seen (or maximum time to observe an incorrect change when
|
|
|
|
|
-- `unchanged` flag is used)
|
2018-08-20 09:51:25 -07:00
|
|
|
|
--
|
|
|
|
|
-- The following keys should be used to expect the state of various ext_
|
|
|
|
|
-- features. Note that an absent key will assert that the item is currently
|
|
|
|
|
-- NOT present on the screen, also when positional form is used.
|
|
|
|
|
--
|
|
|
|
|
-- popupmenu: Expected ext_popupmenu state,
|
|
|
|
|
-- cmdline: Expected ext_cmdline state, as an array of cmdlines of
|
|
|
|
|
-- different level.
|
|
|
|
|
-- cmdline_block: Expected ext_cmdline block (for function definitions)
|
|
|
|
|
-- wildmenu_items: Expected items for ext_wildmenu
|
|
|
|
|
-- wildmenu_pos: Expected position for ext_wildmenu
|
2019-10-13 00:19:57 -07:00
|
|
|
|
function Screen:expect(expected, attr_ids, ...)
|
2018-08-20 09:51:25 -07:00
|
|
|
|
local grid, condition = nil, nil
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local expected_rows = {}
|
2019-08-17 11:52:08 -07:00
|
|
|
|
assert(next({...}) == nil, "invalid args to expect()")
|
2018-08-20 09:51:25 -07:00
|
|
|
|
if type(expected) == "table" then
|
2019-10-13 00:19:57 -07:00
|
|
|
|
assert(not (attr_ids ~= nil))
|
2020-12-23 09:13:13 -07:00
|
|
|
|
local is_key = {grid=true, attr_ids=true, condition=true, mouse_enabled=true,
|
2017-06-26 05:49:15 -07:00
|
|
|
|
any=true, mode=true, unchanged=true, intermediate=true,
|
2021-09-15 16:53:56 -07:00
|
|
|
|
reset=true, timeout=true, request_cb=true, hl_groups=true, extmarks=true}
|
2018-08-31 06:56:53 -07:00
|
|
|
|
for _, v in ipairs(ext_keys) do
|
|
|
|
|
is_key[v] = true
|
|
|
|
|
end
|
2018-08-20 09:51:25 -07:00
|
|
|
|
for k, _ in pairs(expected) do
|
|
|
|
|
if not is_key[k] then
|
|
|
|
|
error("Screen:expect: Unknown keyword argument '"..k.."'")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
grid = expected.grid
|
|
|
|
|
attr_ids = expected.attr_ids
|
|
|
|
|
condition = expected.condition
|
|
|
|
|
assert(not (expected.any ~= nil and grid ~= nil))
|
|
|
|
|
elseif type(expected) == "string" then
|
|
|
|
|
grid = expected
|
|
|
|
|
expected = {}
|
|
|
|
|
elseif type(expected) == "function" then
|
2019-10-13 00:19:57 -07:00
|
|
|
|
assert(not (attr_ids ~= nil))
|
2017-04-04 08:47:23 -07:00
|
|
|
|
condition = expected
|
2018-08-20 09:51:25 -07:00
|
|
|
|
expected = {}
|
2017-04-04 08:47:23 -07:00
|
|
|
|
else
|
2018-08-20 09:51:25 -07:00
|
|
|
|
assert(false)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if grid ~= nil then
|
2017-06-20 10:52:43 -07:00
|
|
|
|
-- Remove the last line and dedent. Note that gsub returns more then one
|
|
|
|
|
-- value.
|
2018-08-20 09:51:25 -07:00
|
|
|
|
grid = dedent(grid:gsub('\n[ ]+$', ''), 0)
|
|
|
|
|
for row in grid:gmatch('[^\n]+') do
|
2017-04-04 08:47:23 -07:00
|
|
|
|
table.insert(expected_rows, row)
|
|
|
|
|
end
|
2017-01-17 08:46:32 -07:00
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local attr_state = {
|
|
|
|
|
ids = attr_ids or self._default_attr_ids,
|
|
|
|
|
}
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_linegrid then
|
|
|
|
|
attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
self._new_attrs = false
|
2017-06-26 05:49:15 -07:00
|
|
|
|
self:_wait(function()
|
2016-06-08 02:26:06 -07:00
|
|
|
|
if condition ~= nil then
|
|
|
|
|
local status, res = pcall(condition)
|
|
|
|
|
if not status then
|
|
|
|
|
return tostring(res)
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-07-11 03:57:01 -07:00
|
|
|
|
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_linegrid and self._new_attrs then
|
|
|
|
|
attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local actual_rows = self:render(not expected.any, attr_state)
|
2016-09-29 17:33:50 -07:00
|
|
|
|
|
2018-08-20 09:51:25 -07:00
|
|
|
|
if expected.any ~= nil then
|
|
|
|
|
-- Search for `any` anywhere in the screen lines.
|
2016-09-29 17:33:50 -07:00
|
|
|
|
local actual_screen_str = table.concat(actual_rows, '\n')
|
2018-08-20 09:51:25 -07:00
|
|
|
|
if nil == string.find(actual_screen_str, expected.any) then
|
2016-02-29 05:48:59 -07:00
|
|
|
|
return (
|
2016-09-29 17:33:50 -07:00
|
|
|
|
'Failed to match any screen lines.\n'
|
2018-08-20 09:51:25 -07:00
|
|
|
|
.. 'Expected (anywhere): "' .. expected.any .. '"\n'
|
2018-12-18 04:50:44 -07:00
|
|
|
|
.. 'Actual:\n |' .. table.concat(actual_rows, '\n |') .. '\n\n')
|
2016-09-29 17:33:50 -07:00
|
|
|
|
end
|
2018-08-20 09:51:25 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if grid ~= nil then
|
2019-08-18 06:06:15 -07:00
|
|
|
|
local err_msg, msg_expected_rows = nil, {}
|
2016-09-29 17:33:50 -07:00
|
|
|
|
-- `expected` must match the screen lines exactly.
|
2017-12-09 03:26:06 -07:00
|
|
|
|
if #actual_rows ~= #expected_rows then
|
2019-08-18 06:06:15 -07:00
|
|
|
|
err_msg = "Expected screen height " .. #expected_rows
|
|
|
|
|
.. ' differs from actual height ' .. #actual_rows .. '.'
|
2017-12-09 03:26:06 -07:00
|
|
|
|
end
|
2019-11-09 23:22:24 -07:00
|
|
|
|
for i, row in ipairs(expected_rows) do
|
|
|
|
|
msg_expected_rows[i] = row
|
2023-02-17 18:27:10 -07:00
|
|
|
|
local pat = nil
|
|
|
|
|
if actual_rows[i] and row ~= actual_rows[i] then
|
|
|
|
|
local after = row
|
|
|
|
|
while true do
|
|
|
|
|
local s, e, m = after:find('{MATCH:(.-)}')
|
|
|
|
|
if not s then
|
|
|
|
|
pat = pat and (pat .. pesc(after))
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
pat = (pat or '') .. pesc(after:sub(1, s - 1)) .. m
|
|
|
|
|
after = after:sub(e + 1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if row ~= actual_rows[i] and (not pat or not actual_rows[i]:match(pat)) then
|
2019-11-09 23:22:24 -07:00
|
|
|
|
msg_expected_rows[i] = '*' .. msg_expected_rows[i]
|
|
|
|
|
if i <= #actual_rows then
|
|
|
|
|
actual_rows[i] = '*' .. actual_rows[i]
|
|
|
|
|
end
|
|
|
|
|
if err_msg == nil then
|
|
|
|
|
err_msg = 'Row ' .. tostring(i) .. ' did not match.'
|
2019-08-18 06:06:15 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if err_msg ~= nil then
|
|
|
|
|
return (
|
|
|
|
|
err_msg..'\nExpected:\n |'..table.concat(msg_expected_rows, '\n |')..'\n'
|
|
|
|
|
..'Actual:\n |'..table.concat(actual_rows, '\n |')..'\n\n'..[[
|
2016-08-13 06:51:53 -07:00
|
|
|
|
To print the expect() call that would assert the current screen state, use
|
2017-06-28 03:54:04 -07:00
|
|
|
|
screen:snapshot_util(). In case of non-deterministic failures, use
|
2016-08-13 06:51:53 -07:00
|
|
|
|
screen:redraw_debug() to show all intermediate screen states. ]])
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
2018-08-20 09:51:25 -07:00
|
|
|
|
|
2019-05-09 14:54:04 -07:00
|
|
|
|
-- UI extensions. The default expectations should cover the case of
|
2018-08-20 09:51:25 -07:00
|
|
|
|
-- the ext_ feature being disabled, or the feature currently not activated
|
2019-05-09 14:54:04 -07:00
|
|
|
|
-- (e.g. no external cmdline visible). Some extensions require
|
2017-06-26 05:49:15 -07:00
|
|
|
|
-- preprocessing to represent highlights in a reproducible way.
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local extstate = self:_extstate_repr(attr_state)
|
2019-07-14 04:26:40 -07:00
|
|
|
|
if expected.mode ~= nil then
|
|
|
|
|
extstate.mode = self.mode
|
2019-05-09 14:54:04 -07:00
|
|
|
|
end
|
2020-12-23 09:13:13 -07:00
|
|
|
|
if expected.mouse_enabled ~= nil then
|
|
|
|
|
extstate.mouse_enabled = self.mouse_enabled
|
|
|
|
|
end
|
2020-01-23 10:05:04 -07:00
|
|
|
|
if expected.win_viewport == nil then
|
|
|
|
|
extstate.win_viewport = nil
|
|
|
|
|
end
|
2019-07-14 04:26:40 -07:00
|
|
|
|
|
2021-05-01 04:29:34 -07:00
|
|
|
|
if expected.float_pos then
|
|
|
|
|
expected.float_pos = deepcopy(expected.float_pos)
|
|
|
|
|
for _, v in pairs(expected.float_pos) do
|
|
|
|
|
if not v.external and v[7] == nil then
|
|
|
|
|
v[7] = 50
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-05-09 14:54:04 -07:00
|
|
|
|
-- Convert assertion errors into invalid screen state descriptions.
|
2020-12-23 09:13:13 -07:00
|
|
|
|
for _, k in ipairs(concat_tables(ext_keys, {'mode', 'mouse_enabled'})) do
|
2019-05-09 14:54:04 -07:00
|
|
|
|
-- Empty states are considered the default and need not be mentioned.
|
|
|
|
|
if (not (expected[k] == nil and isempty(extstate[k]))) then
|
|
|
|
|
local status, res = pcall(eq, expected[k], extstate[k], k)
|
|
|
|
|
if not status then
|
|
|
|
|
return (tostring(res)..'\nHint: full state of "'..k..'":\n '..inspect(extstate[k]))
|
2018-08-31 06:56:53 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
2018-08-20 09:51:25 -07:00
|
|
|
|
end
|
2019-07-14 04:26:40 -07:00
|
|
|
|
|
|
|
|
|
if expected.hl_groups ~= nil then
|
|
|
|
|
for name, id in pairs(expected.hl_groups) do
|
|
|
|
|
local expected_hl = attr_state.ids[id]
|
|
|
|
|
local actual_hl = self._attr_table[self.hl_groups[name]][(self._options.rgb and 1) or 2]
|
|
|
|
|
local status, res = pcall(eq, expected_hl, actual_hl, "highlight "..name)
|
|
|
|
|
if not status then
|
|
|
|
|
return tostring(res)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-09-15 16:53:56 -07:00
|
|
|
|
|
|
|
|
|
if expected.extmarks ~= nil then
|
|
|
|
|
for gridid, expected_marks in pairs(expected.extmarks) do
|
|
|
|
|
local stored_marks = self._grid_win_extmarks[gridid]
|
|
|
|
|
if stored_marks == nil then
|
|
|
|
|
return 'no win_extmark for grid '..tostring(gridid)
|
|
|
|
|
end
|
|
|
|
|
local status, res = pcall(eq, expected_marks, stored_marks, "extmarks for grid "..tostring(gridid))
|
|
|
|
|
if not status then
|
|
|
|
|
return tostring(res)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
for gridid, _ in pairs(self._grid_win_extmarks) do
|
|
|
|
|
local expected_marks = expected.extmarks[gridid]
|
|
|
|
|
if expected_marks == nil then
|
|
|
|
|
return 'unexpected win_extmark for grid '..tostring(gridid)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-06-26 05:49:15 -07:00
|
|
|
|
end, expected)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2023-02-14 16:26:55 -07:00
|
|
|
|
function Screen:expect_unchanged(intermediate, waittime_ms, ignore_attrs)
|
2019-07-20 05:01:33 -07:00
|
|
|
|
waittime_ms = waittime_ms and waittime_ms or 100
|
|
|
|
|
-- Collect the current screen state.
|
|
|
|
|
local kwargs = self:get_snapshot(nil, ignore_attrs)
|
2019-07-23 17:50:24 -07:00
|
|
|
|
|
2023-02-14 16:26:55 -07:00
|
|
|
|
if intermediate then
|
|
|
|
|
kwargs.intermediate = true
|
|
|
|
|
else
|
|
|
|
|
kwargs.unchanged = true
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-23 17:50:24 -07:00
|
|
|
|
kwargs.timeout = waittime_ms
|
2023-02-14 16:26:55 -07:00
|
|
|
|
-- Check that screen state does not change.
|
2019-07-20 05:01:33 -07:00
|
|
|
|
self:expect(kwargs)
|
|
|
|
|
end
|
|
|
|
|
|
2017-06-26 05:49:15 -07:00
|
|
|
|
function Screen:_wait(check, flags)
|
|
|
|
|
local err, checked = false, false
|
2015-03-22 07:23:53 -07:00
|
|
|
|
local success_seen = false
|
|
|
|
|
local failure_after_success = false
|
2018-09-29 04:28:53 -07:00
|
|
|
|
local did_flush = true
|
2017-06-26 05:49:15 -07:00
|
|
|
|
local warn_immediate = not (flags.unchanged or flags.intermediate)
|
|
|
|
|
|
|
|
|
|
if flags.intermediate and flags.unchanged then
|
|
|
|
|
error("Choose only one of 'intermediate' and 'unchanged', not both")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if flags.reset then
|
|
|
|
|
-- throw away all state, we expect it to be retransmitted
|
|
|
|
|
self:_reset()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Maximum timeout, after which a incorrect state will be regarded as a
|
|
|
|
|
-- failure
|
|
|
|
|
local timeout = flags.timeout or self.timeout
|
|
|
|
|
|
|
|
|
|
-- Minimal timeout before the loop is allowed to be stopped so we
|
|
|
|
|
-- always do some check for failure after success.
|
|
|
|
|
local minimal_timeout = default_timeout_factor * 2
|
|
|
|
|
|
|
|
|
|
local immediate_seen, intermediate_seen = false, false
|
|
|
|
|
if not check() then
|
|
|
|
|
minimal_timeout = default_timeout_factor * 20
|
|
|
|
|
immediate_seen = true
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-20 05:01:33 -07:00
|
|
|
|
-- For an "unchanged" test, flags.timeout is the time during which the state
|
|
|
|
|
-- must not change, so always wait this full time.
|
2017-06-26 05:49:15 -07:00
|
|
|
|
if (flags.unchanged or flags.intermediate) and flags.timeout ~= nil then
|
|
|
|
|
minimal_timeout = timeout
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
assert(timeout >= minimal_timeout)
|
|
|
|
|
local did_minimal_timeout = false
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local function notification_cb(method, args)
|
2019-08-15 10:08:47 -07:00
|
|
|
|
assert(method == 'redraw', string.format(
|
|
|
|
|
'notification_cb: unexpected method (%s, args=%s)', method, inspect(args)))
|
2018-09-29 04:28:53 -07:00
|
|
|
|
did_flush = self:_redraw(args)
|
|
|
|
|
if not did_flush then
|
|
|
|
|
return
|
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
err = check()
|
2014-12-09 10:12:12 -07:00
|
|
|
|
checked = true
|
2017-06-26 05:49:15 -07:00
|
|
|
|
if err and immediate_seen then
|
|
|
|
|
intermediate_seen = true
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
if not err then
|
2015-03-22 07:23:53 -07:00
|
|
|
|
success_seen = true
|
2017-06-26 05:49:15 -07:00
|
|
|
|
if did_minimal_timeout then
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self._session:stop()
|
2017-06-26 05:49:15 -07:00
|
|
|
|
end
|
2015-03-22 07:23:53 -07:00
|
|
|
|
elseif success_seen and #args > 0 then
|
2023-03-01 05:16:57 -07:00
|
|
|
|
success_seen = false
|
2015-03-22 07:23:53 -07:00
|
|
|
|
failure_after_success = true
|
2019-07-12 15:50:52 -07:00
|
|
|
|
-- print(inspect(args))
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
2015-03-22 07:23:53 -07:00
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
return true
|
|
|
|
|
end
|
2022-07-25 01:16:33 -07:00
|
|
|
|
local eof = run_session(self._session, flags.request_cb, notification_cb, nil, minimal_timeout)
|
2018-09-29 04:28:53 -07:00
|
|
|
|
if not did_flush then
|
|
|
|
|
err = "no flush received"
|
|
|
|
|
elseif not checked then
|
2014-12-09 10:12:12 -07:00
|
|
|
|
err = check()
|
2017-06-26 05:49:15 -07:00
|
|
|
|
if not err and flags.unchanged then
|
|
|
|
|
-- expecting NO screen change: use a shorter timeout
|
|
|
|
|
success_seen = true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-25 01:16:33 -07:00
|
|
|
|
if not success_seen and not eof then
|
2017-06-26 05:49:15 -07:00
|
|
|
|
did_minimal_timeout = true
|
2022-07-25 01:16:33 -07:00
|
|
|
|
eof = run_session(self._session, flags.request_cb, notification_cb, nil, timeout-minimal_timeout)
|
2017-06-26 05:49:15 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local did_warn = false
|
|
|
|
|
if warn_immediate and immediate_seen then
|
|
|
|
|
print([[
|
|
|
|
|
|
2018-11-15 18:00:04 -07:00
|
|
|
|
warning: Screen test succeeded immediately. Try to avoid this unless the
|
2017-06-26 05:49:15 -07:00
|
|
|
|
purpose of the test really requires it.]])
|
|
|
|
|
if intermediate_seen then
|
|
|
|
|
print([[
|
|
|
|
|
There are intermediate states between the two identical expects.
|
|
|
|
|
Use screen:snapshot_util() or screen:redraw_debug() to find them, and add them
|
|
|
|
|
to the test if they make sense.
|
|
|
|
|
]])
|
|
|
|
|
else
|
2018-11-15 18:00:04 -07:00
|
|
|
|
print([[If necessary, silence this warning with 'unchanged' argument of screen:expect.]])
|
2017-06-26 05:49:15 -07:00
|
|
|
|
end
|
|
|
|
|
did_warn = true
|
2014-12-09 10:12:12 -07:00
|
|
|
|
end
|
2015-03-22 07:23:53 -07:00
|
|
|
|
|
|
|
|
|
if failure_after_success then
|
|
|
|
|
print([[
|
2015-07-07 18:58:32 -07:00
|
|
|
|
|
2018-11-15 18:00:04 -07:00
|
|
|
|
warning: Screen changes were received after the expected state. This indicates
|
2022-03-02 07:30:35 -07:00
|
|
|
|
indeterminism in the test. Try adding screen:expect(...) (or poke_eventloop())
|
|
|
|
|
between asynchronous (feed(), nvim_input()) and synchronous API calls.
|
2018-11-15 18:00:04 -07:00
|
|
|
|
- Use screen:redraw_debug() to investigate; it may find relevant intermediate
|
|
|
|
|
states that should be added to the test to make it more robust.
|
|
|
|
|
- If the purpose of the test is to assert state after some user input sent
|
|
|
|
|
with feed(), adding screen:expect() before the feed() will help to ensure
|
|
|
|
|
the input is sent when Nvim is in a predictable state. This is preferable
|
2022-03-02 07:30:35 -07:00
|
|
|
|
to poke_eventloop(), for being closer to real user interaction.
|
|
|
|
|
- poke_eventloop() can trigger redraws and thus generate more indeterminism.
|
|
|
|
|
Try removing poke_eventloop().
|
2015-03-22 07:23:53 -07:00
|
|
|
|
]])
|
2017-06-26 05:49:15 -07:00
|
|
|
|
did_warn = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err then
|
2022-07-25 01:16:33 -07:00
|
|
|
|
if eof then err = err..'\n\n'..eof[2] end
|
2019-10-19 14:15:07 -07:00
|
|
|
|
busted.fail(err, 3)
|
2017-06-26 05:49:15 -07:00
|
|
|
|
elseif did_warn then
|
2022-07-25 01:16:33 -07:00
|
|
|
|
if eof then print(eof[2]) end
|
2015-03-22 07:23:53 -07:00
|
|
|
|
local tb = debug.traceback()
|
|
|
|
|
local index = string.find(tb, '\n%s*%[C]')
|
|
|
|
|
print(string.sub(tb,1,index))
|
|
|
|
|
end
|
|
|
|
|
|
2017-06-26 05:49:15 -07:00
|
|
|
|
if flags.intermediate then
|
|
|
|
|
assert(intermediate_seen, "expected intermediate screen state before final screen state")
|
|
|
|
|
elseif flags.unchanged then
|
|
|
|
|
assert(not intermediate_seen, "expected screen state to be unchanged")
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-01-24 11:15:39 -07:00
|
|
|
|
function Screen:sleep(ms, request_cb)
|
2017-06-26 05:49:15 -07:00
|
|
|
|
local function notification_cb(method, args)
|
|
|
|
|
assert(method == 'redraw')
|
|
|
|
|
self:_redraw(args)
|
|
|
|
|
end
|
2019-01-24 11:15:39 -07:00
|
|
|
|
run_session(self._session, request_cb, notification_cb, nil, ms)
|
2016-05-22 12:53:43 -07:00
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
function Screen:_redraw(updates)
|
2018-09-29 04:28:53 -07:00
|
|
|
|
local did_flush = false
|
|
|
|
|
for k, update in ipairs(updates) do
|
2019-07-12 15:50:52 -07:00
|
|
|
|
-- print('--', inspect(update))
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local method = update[1]
|
|
|
|
|
for i = 2, #update do
|
2017-04-21 05:58:52 -07:00
|
|
|
|
local handler_name = '_handle_'..method
|
|
|
|
|
local handler = self[handler_name]
|
2019-12-01 02:06:10 -07:00
|
|
|
|
assert(handler ~= nil, "missing handler: Screen:"..handler_name)
|
|
|
|
|
local status, res = pcall(handler, self, unpack(update[i]))
|
|
|
|
|
if not status then
|
|
|
|
|
error(handler_name..' failed'
|
|
|
|
|
..'\n payload: '..inspect(update)
|
|
|
|
|
..'\n error: '..tostring(res))
|
2016-06-08 02:26:06 -07:00
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
2018-09-29 04:28:53 -07:00
|
|
|
|
if k == #updates and method == "flush" then
|
|
|
|
|
did_flush = true
|
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
2018-09-29 04:28:53 -07:00
|
|
|
|
return did_flush
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_resize(width, height)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self:_handle_grid_resize(1, width, height)
|
|
|
|
|
self._scroll_region = {
|
|
|
|
|
top = 1, bot = height, left = 1, right = width
|
|
|
|
|
}
|
|
|
|
|
self._grid = self._grids[1]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function min(x,y)
|
|
|
|
|
if x < y then
|
|
|
|
|
return x
|
|
|
|
|
else
|
|
|
|
|
return y
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_grid_resize(grid, width, height)
|
2015-01-15 05:01:25 -07:00
|
|
|
|
local rows = {}
|
2015-11-17 14:44:00 -07:00
|
|
|
|
for _ = 1, height do
|
2015-01-15 05:01:25 -07:00
|
|
|
|
local cols = {}
|
2015-11-17 14:44:00 -07:00
|
|
|
|
for _ = 1, width do
|
2018-06-17 04:31:13 -07:00
|
|
|
|
table.insert(cols, {text = ' ', attrs = self._clear_attrs, hl_id = 0})
|
2015-01-15 05:01:25 -07:00
|
|
|
|
end
|
|
|
|
|
table.insert(rows, cols)
|
|
|
|
|
end
|
2017-12-09 03:26:06 -07:00
|
|
|
|
if grid > 1 and self._grids[grid] ~= nil then
|
|
|
|
|
local old = self._grids[grid]
|
|
|
|
|
for i = 1, min(height,old.height) do
|
|
|
|
|
for j = 1, min(width,old.width) do
|
|
|
|
|
rows[i][j] = old.rows[i][j]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if self._cursor.grid == grid then
|
2017-04-26 06:28:10 -07:00
|
|
|
|
self._cursor.row = 1 -- -1 ?
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self._cursor.col = 1
|
|
|
|
|
end
|
|
|
|
|
self._grids[grid] = {
|
|
|
|
|
rows=rows,
|
|
|
|
|
width=width,
|
|
|
|
|
height=height,
|
2015-01-15 05:01:25 -07:00
|
|
|
|
}
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-10-19 01:42:34 -07:00
|
|
|
|
|
2019-09-01 02:25:00 -07:00
|
|
|
|
function Screen:_handle_msg_set_pos(grid, row, scrolled, char)
|
|
|
|
|
self.msg_grid = grid
|
|
|
|
|
self.msg_grid_pos = row
|
|
|
|
|
self.msg_scrolled = scrolled
|
|
|
|
|
self.msg_sep_char = char
|
2018-10-19 01:42:34 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-09-29 04:28:53 -07:00
|
|
|
|
function Screen:_handle_flush()
|
|
|
|
|
end
|
|
|
|
|
|
2017-06-26 05:49:15 -07:00
|
|
|
|
function Screen:_reset()
|
|
|
|
|
-- TODO: generalize to multigrid later
|
|
|
|
|
self:_handle_grid_clear(1)
|
|
|
|
|
|
|
|
|
|
-- TODO: share with initialization, so it generalizes?
|
|
|
|
|
self.popupmenu = nil
|
|
|
|
|
self.cmdline = {}
|
|
|
|
|
self.cmdline_block = {}
|
|
|
|
|
self.wildmenu_items = nil
|
|
|
|
|
self.wildmenu_pos = nil
|
2021-09-15 16:53:56 -07:00
|
|
|
|
self._grid_win_extmarks = {}
|
2017-06-26 05:49:15 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-04-18 04:42:04 -07:00
|
|
|
|
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
|
|
|
|
|
self._cursor_style_enabled = cursor_style_enabled
|
2018-07-26 12:27:41 -07:00
|
|
|
|
for _, item in pairs(mode_info) do
|
|
|
|
|
-- attr IDs are not stable, but their value should be
|
|
|
|
|
if item.attr_id ~= nil then
|
|
|
|
|
item.attr = self._attr_table[item.attr_id][1]
|
|
|
|
|
item.attr_id = nil
|
|
|
|
|
end
|
|
|
|
|
if item.attr_id_lm ~= nil then
|
|
|
|
|
item.attr_lm = self._attr_table[item.attr_id_lm][1]
|
|
|
|
|
item.attr_id_lm = nil
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-04-18 04:42:04 -07:00
|
|
|
|
self._mode_info = mode_info
|
2017-03-20 14:56:58 -07:00
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
function Screen:_handle_clear()
|
2018-07-24 02:54:09 -07:00
|
|
|
|
-- the first implemented UI protocol clients (python-gui and builitin TUI)
|
|
|
|
|
-- allowed the cleared region to be restricted by setting the scroll region.
|
|
|
|
|
-- this was never used by nvim tough, and not documented and implemented by
|
|
|
|
|
-- newer clients, to check we remain compatible with both kind of clients,
|
|
|
|
|
-- ensure the scroll region is in a reset state.
|
|
|
|
|
local expected_region = {
|
2017-12-09 03:26:06 -07:00
|
|
|
|
top = 1, bot = self._grid.height, left = 1, right = self._grid.width
|
2018-07-24 02:54:09 -07:00
|
|
|
|
}
|
|
|
|
|
eq(expected_region, self._scroll_region)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self:_handle_grid_clear(1)
|
2018-07-06 05:39:50 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_grid_clear(grid)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self:_clear_block(self._grids[grid], 1, self._grids[grid].height, 1, self._grids[grid].width)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_grid_destroy(grid)
|
|
|
|
|
self._grids[grid] = nil
|
2018-11-17 10:23:42 -07:00
|
|
|
|
if self._options.ext_multigrid then
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self.win_position[grid] = nil
|
2020-01-23 10:05:04 -07:00
|
|
|
|
self.win_viewport[grid] = nil
|
2017-12-09 03:26:06 -07:00
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_eol_clear()
|
|
|
|
|
local row, col = self._cursor.row, self._cursor.col
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self:_clear_block(self._grid, row, row, col, self._grid.width)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cursor_goto(row, col)
|
|
|
|
|
self._cursor.row = row + 1
|
|
|
|
|
self._cursor.col = col + 1
|
|
|
|
|
end
|
|
|
|
|
|
2018-07-06 05:39:50 -07:00
|
|
|
|
function Screen:_handle_grid_cursor_goto(grid, row, col)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self._cursor.grid = grid
|
2022-09-26 02:40:06 -07:00
|
|
|
|
assert(row >= 0 and col >= 0)
|
2018-07-06 05:39:50 -07:00
|
|
|
|
self._cursor.row = row + 1
|
|
|
|
|
self._cursor.col = col + 1
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-26 06:28:10 -07:00
|
|
|
|
function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
|
2020-01-23 10:05:04 -07:00
|
|
|
|
self.win_position[grid] = {
|
|
|
|
|
win = win,
|
|
|
|
|
startrow = startrow,
|
|
|
|
|
startcol = startcol,
|
|
|
|
|
width = width,
|
|
|
|
|
height = height
|
|
|
|
|
}
|
|
|
|
|
self.float_pos[grid] = nil
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-12 15:58:46 -07:00
|
|
|
|
function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol, linecount, scroll_delta)
|
|
|
|
|
-- accumulate scroll delta
|
|
|
|
|
local last_scroll_delta = self.win_viewport[grid] and self.win_viewport[grid].sum_scroll_delta or 0
|
2020-01-23 10:05:04 -07:00
|
|
|
|
self.win_viewport[grid] = {
|
|
|
|
|
win = win,
|
|
|
|
|
topline = topline,
|
|
|
|
|
botline = botline,
|
|
|
|
|
curline = curline,
|
2021-09-10 19:19:39 -07:00
|
|
|
|
curcol = curcol,
|
2023-03-12 15:58:46 -07:00
|
|
|
|
linecount = linecount,
|
|
|
|
|
sum_scroll_delta = scroll_delta + last_scroll_delta
|
2020-01-23 10:05:04 -07:00
|
|
|
|
}
|
2017-04-26 06:28:10 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_win_float_pos(grid, ...)
|
|
|
|
|
self.win_position[grid] = nil
|
|
|
|
|
self.float_pos[grid] = {...}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_win_external_pos(grid)
|
|
|
|
|
self.win_position[grid] = nil
|
|
|
|
|
self.float_pos[grid] = {external=true}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_win_hide(grid)
|
|
|
|
|
self.win_position[grid] = nil
|
|
|
|
|
self.float_pos[grid] = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_win_close(grid)
|
|
|
|
|
self.float_pos[grid] = nil
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-15 16:53:56 -07:00
|
|
|
|
function Screen:_handle_win_extmark(grid, ...)
|
|
|
|
|
if self._grid_win_extmarks[grid] == nil then
|
|
|
|
|
self._grid_win_extmarks[grid] = {}
|
|
|
|
|
end
|
|
|
|
|
table.insert(self._grid_win_extmarks[grid], {...})
|
|
|
|
|
end
|
|
|
|
|
|
2015-03-15 06:21:05 -07:00
|
|
|
|
function Screen:_handle_busy_start()
|
|
|
|
|
self._busy = true
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2015-03-15 06:21:05 -07:00
|
|
|
|
function Screen:_handle_busy_stop()
|
|
|
|
|
self._busy = false
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_mouse_on()
|
2020-12-23 09:13:13 -07:00
|
|
|
|
self.mouse_enabled = true
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_mouse_off()
|
2020-12-23 09:13:13 -07:00
|
|
|
|
self.mouse_enabled = false
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-04-17 04:32:22 -07:00
|
|
|
|
function Screen:_handle_mode_change(mode, idx)
|
2017-04-18 04:42:04 -07:00
|
|
|
|
assert(mode == self._mode_info[idx+1].name)
|
2016-11-25 03:33:57 -07:00
|
|
|
|
self.mode = mode
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_set_scroll_region(top, bot, left, right)
|
|
|
|
|
self._scroll_region.top = top + 1
|
|
|
|
|
self._scroll_region.bot = bot + 1
|
|
|
|
|
self._scroll_region.left = left + 1
|
|
|
|
|
self._scroll_region.right = right + 1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_scroll(count)
|
|
|
|
|
local top = self._scroll_region.top
|
|
|
|
|
local bot = self._scroll_region.bot
|
|
|
|
|
local left = self._scroll_region.left
|
|
|
|
|
local right = self._scroll_region.right
|
2018-07-24 02:54:09 -07:00
|
|
|
|
self:_handle_grid_scroll(1, top-1, bot, left-1, right, count, 0)
|
|
|
|
|
end
|
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
function Screen:_handle_grid_scroll(g, top, bot, left, right, rows, cols)
|
2018-07-24 02:54:09 -07:00
|
|
|
|
top = top+1
|
|
|
|
|
left = left+1
|
|
|
|
|
assert(cols == 0)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local grid = self._grids[g]
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local start, stop, step
|
|
|
|
|
|
2018-11-18 02:00:27 -07:00
|
|
|
|
|
2018-07-24 02:54:09 -07:00
|
|
|
|
if rows > 0 then
|
2014-12-08 18:31:45 -07:00
|
|
|
|
start = top
|
2018-07-24 02:54:09 -07:00
|
|
|
|
stop = bot - rows
|
2014-12-08 18:31:45 -07:00
|
|
|
|
step = 1
|
|
|
|
|
else
|
|
|
|
|
start = bot
|
2018-07-24 02:54:09 -07:00
|
|
|
|
stop = top - rows
|
2014-12-08 18:31:45 -07:00
|
|
|
|
step = -1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- shift scroll region
|
|
|
|
|
for i = start, stop, step do
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local target = grid.rows[i]
|
|
|
|
|
local source = grid.rows[i + rows]
|
2015-01-15 05:01:25 -07:00
|
|
|
|
for j = left, right do
|
|
|
|
|
target[j].text = source[j].text
|
|
|
|
|
target[j].attrs = source[j].attrs
|
2018-06-17 04:31:13 -07:00
|
|
|
|
target[j].hl_id = source[j].hl_id
|
2015-01-15 05:01:25 -07:00
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- clear invalid rows
|
2018-07-24 02:54:09 -07:00
|
|
|
|
for i = stop + step, stop + rows, step do
|
2019-09-06 10:33:12 -07:00
|
|
|
|
self:_clear_row_section(grid, i, left, right, true)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-07-06 05:39:50 -07:00
|
|
|
|
function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info)
|
|
|
|
|
self._attr_table[id] = {rgb_attrs, cterm_attrs}
|
2018-06-17 04:31:13 -07:00
|
|
|
|
self._hl_info[id] = info
|
2018-07-06 05:39:50 -07:00
|
|
|
|
self._new_attrs = true
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-14 04:26:40 -07:00
|
|
|
|
function Screen:_handle_hl_group_set(name, id)
|
|
|
|
|
self.hl_groups[name] = id
|
|
|
|
|
end
|
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
function Screen:get_hl(val)
|
|
|
|
|
if self._options.ext_newgrid then
|
|
|
|
|
return self._attr_table[val][1]
|
|
|
|
|
else
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
function Screen:_handle_highlight_set(attrs)
|
|
|
|
|
self._attrs = attrs
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_put(str)
|
2018-09-28 05:19:37 -07:00
|
|
|
|
assert(not self._options.ext_linegrid)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local cell = self._grid.rows[self._cursor.row][self._cursor.col]
|
2014-12-08 18:31:45 -07:00
|
|
|
|
cell.text = str
|
|
|
|
|
cell.attrs = self._attrs
|
2018-06-17 04:31:13 -07:00
|
|
|
|
cell.hl_id = -1
|
2014-12-08 18:31:45 -07:00
|
|
|
|
self._cursor.col = self._cursor.col + 1
|
|
|
|
|
end
|
|
|
|
|
|
2018-07-06 05:39:50 -07:00
|
|
|
|
function Screen:_handle_grid_line(grid, row, col, items)
|
2018-09-28 05:19:37 -07:00
|
|
|
|
assert(self._options.ext_linegrid)
|
2023-06-03 19:49:02 -07:00
|
|
|
|
assert(#items > 0)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local line = self._grids[grid].rows[row+1]
|
2018-07-06 05:39:50 -07:00
|
|
|
|
local colpos = col+1
|
2018-06-17 04:31:13 -07:00
|
|
|
|
local hl_id = 0
|
2018-07-06 05:39:50 -07:00
|
|
|
|
for _,item in ipairs(items) do
|
2018-06-17 04:31:13 -07:00
|
|
|
|
local text, hl_id_cell, count = unpack(item)
|
|
|
|
|
if hl_id_cell ~= nil then
|
|
|
|
|
hl_id = hl_id_cell
|
2018-07-06 05:39:50 -07:00
|
|
|
|
end
|
|
|
|
|
for _ = 1, (count or 1) do
|
|
|
|
|
local cell = line[colpos]
|
|
|
|
|
cell.text = text
|
2018-06-17 04:31:13 -07:00
|
|
|
|
cell.hl_id = hl_id
|
2018-07-06 05:39:50 -07:00
|
|
|
|
colpos = colpos+1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
function Screen:_handle_bell()
|
2015-01-15 05:01:25 -07:00
|
|
|
|
self.bell = true
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_visual_bell()
|
2015-01-15 05:01:25 -07:00
|
|
|
|
self.visual_bell = true
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-09-20 03:06:19 -07:00
|
|
|
|
function Screen:_handle_default_colors_set(rgb_fg, rgb_bg, rgb_sp, cterm_fg, cterm_bg)
|
|
|
|
|
self.default_colors = {
|
|
|
|
|
rgb_fg=rgb_fg,
|
|
|
|
|
rgb_bg=rgb_bg,
|
|
|
|
|
rgb_sp=rgb_sp,
|
|
|
|
|
cterm_fg=cterm_fg,
|
|
|
|
|
cterm_bg=cterm_bg
|
|
|
|
|
}
|
2018-02-06 11:46:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2014-12-12 12:25:11 -07:00
|
|
|
|
function Screen:_handle_update_fg(fg)
|
|
|
|
|
self._fg = fg
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_update_bg(bg)
|
|
|
|
|
self._bg = bg
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-23 15:01:15 -07:00
|
|
|
|
function Screen:_handle_update_sp(sp)
|
|
|
|
|
self._sp = sp
|
|
|
|
|
end
|
|
|
|
|
|
2015-01-12 08:14:52 -07:00
|
|
|
|
function Screen:_handle_suspend()
|
2015-01-15 05:01:25 -07:00
|
|
|
|
self.suspended = true
|
2014-12-12 12:25:11 -07:00
|
|
|
|
end
|
|
|
|
|
|
2015-07-10 16:03:30 -07:00
|
|
|
|
function Screen:_handle_update_menu()
|
|
|
|
|
self.update_menu = true
|
|
|
|
|
end
|
|
|
|
|
|
2015-01-13 17:19:59 -07:00
|
|
|
|
function Screen:_handle_set_title(title)
|
2015-01-15 05:01:25 -07:00
|
|
|
|
self.title = title
|
2015-01-13 17:19:59 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_set_icon(icon)
|
2015-01-15 05:01:25 -07:00
|
|
|
|
self.icon = icon
|
2015-01-13 17:19:59 -07:00
|
|
|
|
end
|
|
|
|
|
|
2017-12-12 10:23:19 -07:00
|
|
|
|
function Screen:_handle_option_set(name, value)
|
|
|
|
|
self.options[name] = value
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-26 06:28:10 -07:00
|
|
|
|
function Screen:_handle_popupmenu_show(items, selected, row, col, grid)
|
2019-03-04 02:59:44 -07:00
|
|
|
|
self.popupmenu = {items=items, pos=selected, anchor={grid, row, col}}
|
2018-08-20 09:51:25 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_popupmenu_select(selected)
|
|
|
|
|
self.popupmenu.pos = selected
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_popupmenu_hide()
|
|
|
|
|
self.popupmenu = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level)
|
|
|
|
|
if firstc == '' then firstc = nil end
|
|
|
|
|
if prompt == '' then prompt = nil end
|
|
|
|
|
if indent == 0 then indent = nil end
|
2019-06-25 22:22:19 -07:00
|
|
|
|
|
|
|
|
|
-- check position is valid #10000
|
|
|
|
|
local len = 0
|
|
|
|
|
for _, chunk in ipairs(content) do
|
|
|
|
|
len = len + string.len(chunk[2])
|
|
|
|
|
end
|
|
|
|
|
assert(pos <= len)
|
|
|
|
|
|
2018-08-20 09:51:25 -07:00
|
|
|
|
self.cmdline[level] = {content=content, pos=pos, firstc=firstc,
|
|
|
|
|
prompt=prompt, indent=indent}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_hide(level)
|
|
|
|
|
self.cmdline[level] = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_special_char(char, shift, level)
|
|
|
|
|
-- cleared by next cmdline_show on the same level
|
|
|
|
|
self.cmdline[level].special = {char, shift}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_pos(pos, level)
|
|
|
|
|
self.cmdline[level].pos = pos
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_block_show(block)
|
|
|
|
|
self.cmdline_block = block
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_block_append(item)
|
|
|
|
|
self.cmdline_block[#self.cmdline_block+1] = item
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_cmdline_block_hide()
|
|
|
|
|
self.cmdline_block = {}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_wildmenu_show(items)
|
|
|
|
|
self.wildmenu_items = items
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_wildmenu_select(pos)
|
|
|
|
|
self.wildmenu_pos = pos
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_wildmenu_hide()
|
|
|
|
|
self.wildmenu_items, self.wildmenu_pos = nil, nil
|
|
|
|
|
end
|
|
|
|
|
|
2017-10-31 08:46:02 -07:00
|
|
|
|
function Screen:_handle_msg_show(kind, chunks, replace_last)
|
|
|
|
|
local pos = #self.messages
|
|
|
|
|
if not replace_last or pos == 0 then
|
|
|
|
|
pos = pos + 1
|
|
|
|
|
end
|
|
|
|
|
self.messages[pos] = {kind=kind, content=chunks}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_msg_clear()
|
|
|
|
|
self.messages = {}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_msg_showcmd(msg)
|
|
|
|
|
self.showcmd = msg
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_msg_showmode(msg)
|
|
|
|
|
self.showmode = msg
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_msg_ruler(msg)
|
|
|
|
|
self.ruler = msg
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_handle_msg_history_show(entries)
|
|
|
|
|
self.msg_history = entries
|
|
|
|
|
end
|
|
|
|
|
|
2021-01-20 02:25:52 -07:00
|
|
|
|
function Screen:_handle_msg_history_clear()
|
|
|
|
|
self.msg_history = {}
|
|
|
|
|
end
|
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
function Screen:_clear_block(grid, top, bot, left, right)
|
2015-01-16 13:22:32 -07:00
|
|
|
|
for i = top, bot do
|
2017-12-09 03:26:06 -07:00
|
|
|
|
self:_clear_row_section(grid, i, left, right)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-09-06 10:33:12 -07:00
|
|
|
|
function Screen:_clear_row_section(grid, rownum, startcol, stopcol, invalid)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local row = grid.rows[rownum]
|
2014-12-08 18:31:45 -07:00
|
|
|
|
for i = startcol, stopcol do
|
2019-09-06 10:33:12 -07:00
|
|
|
|
row[i].text = (invalid and '<EFBFBD>' or ' ')
|
2018-07-06 05:39:50 -07:00
|
|
|
|
row[i].attrs = self._clear_attrs
|
2019-10-11 10:27:15 -07:00
|
|
|
|
row[i].hl_id = 0
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-11-17 10:23:42 -07:00
|
|
|
|
function Screen:_row_repr(gridnr, rownr, attr_state, cursor)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
local rv = {}
|
|
|
|
|
local current_attr_id
|
2018-11-17 10:23:42 -07:00
|
|
|
|
local i = 1
|
|
|
|
|
local has_windows = self._options.ext_multigrid and gridnr == 1
|
|
|
|
|
local row = self._grids[gridnr].rows[rownr]
|
2019-09-01 02:25:00 -07:00
|
|
|
|
if has_windows and self.msg_grid and self.msg_grid_pos < rownr then
|
|
|
|
|
return '['..self.msg_grid..':'..string.rep('-',#row)..']'
|
|
|
|
|
end
|
2018-11-17 10:23:42 -07:00
|
|
|
|
while i <= #row do
|
|
|
|
|
local did_window = false
|
|
|
|
|
if has_windows then
|
|
|
|
|
for id,pos in pairs(self.win_position) do
|
|
|
|
|
if i-1 == pos.startcol and pos.startrow <= rownr-1 and rownr-1 < pos.startrow + pos.height then
|
|
|
|
|
if current_attr_id then
|
|
|
|
|
-- close current attribute bracket
|
|
|
|
|
table.insert(rv, '}')
|
|
|
|
|
current_attr_id = nil
|
|
|
|
|
end
|
|
|
|
|
table.insert(rv, '['..id..':'..string.rep('-',pos.width)..']')
|
|
|
|
|
i = i + pos.width
|
|
|
|
|
did_window = true
|
|
|
|
|
end
|
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
2018-11-17 10:23:42 -07:00
|
|
|
|
|
|
|
|
|
if not did_window then
|
2019-10-11 10:27:15 -07:00
|
|
|
|
local attr_id = self:_get_attr_id(attr_state, row[i].attrs, row[i].hl_id)
|
2018-11-17 10:23:42 -07:00
|
|
|
|
if current_attr_id and attr_id ~= current_attr_id then
|
|
|
|
|
-- close current attribute bracket
|
|
|
|
|
table.insert(rv, '}')
|
|
|
|
|
current_attr_id = nil
|
|
|
|
|
end
|
|
|
|
|
if not current_attr_id and attr_id then
|
|
|
|
|
-- open a new attribute bracket
|
|
|
|
|
table.insert(rv, '{' .. attr_id .. ':')
|
|
|
|
|
current_attr_id = attr_id
|
|
|
|
|
end
|
|
|
|
|
if not self._busy and cursor and self._cursor.col == i then
|
|
|
|
|
table.insert(rv, '^')
|
|
|
|
|
end
|
|
|
|
|
table.insert(rv, row[i].text)
|
|
|
|
|
i = i + 1
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if current_attr_id then
|
|
|
|
|
table.insert(rv, '}')
|
|
|
|
|
end
|
|
|
|
|
-- return the line representation, but remove empty attribute brackets and
|
|
|
|
|
-- trailing whitespace
|
|
|
|
|
return table.concat(rv, '')--:gsub('%s+$', '')
|
|
|
|
|
end
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
function Screen:_extstate_repr(attr_state)
|
|
|
|
|
local cmdline = {}
|
|
|
|
|
for i, entry in pairs(self.cmdline) do
|
|
|
|
|
entry = shallowcopy(entry)
|
|
|
|
|
entry.content = self:_chunks_repr(entry.content, attr_state)
|
|
|
|
|
cmdline[i] = entry
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local cmdline_block = {}
|
|
|
|
|
for i, entry in ipairs(self.cmdline_block) do
|
|
|
|
|
cmdline_block[i] = self:_chunks_repr(entry, attr_state)
|
|
|
|
|
end
|
|
|
|
|
|
2017-10-31 08:46:02 -07:00
|
|
|
|
local messages = {}
|
|
|
|
|
for i, entry in ipairs(self.messages) do
|
|
|
|
|
messages[i] = {kind=entry.kind, content=self:_chunks_repr(entry.content, attr_state)}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local msg_history = {}
|
|
|
|
|
for i, entry in ipairs(self.msg_history) do
|
2021-01-20 02:25:52 -07:00
|
|
|
|
msg_history[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)}
|
2017-10-31 08:46:02 -07:00
|
|
|
|
end
|
|
|
|
|
|
2020-01-23 10:05:04 -07:00
|
|
|
|
local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
return {
|
|
|
|
|
popupmenu=self.popupmenu,
|
|
|
|
|
cmdline=cmdline,
|
|
|
|
|
cmdline_block=cmdline_block,
|
|
|
|
|
wildmenu_items=self.wildmenu_items,
|
|
|
|
|
wildmenu_pos=self.wildmenu_pos,
|
2017-10-31 08:46:02 -07:00
|
|
|
|
messages=messages,
|
|
|
|
|
showmode=self:_chunks_repr(self.showmode, attr_state),
|
|
|
|
|
showcmd=self:_chunks_repr(self.showcmd, attr_state),
|
|
|
|
|
ruler=self:_chunks_repr(self.ruler, attr_state),
|
|
|
|
|
msg_history=msg_history,
|
2020-01-23 10:05:04 -07:00
|
|
|
|
float_pos=self.float_pos,
|
|
|
|
|
win_viewport=win_viewport,
|
2018-08-31 06:56:53 -07:00
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_chunks_repr(chunks, attr_state)
|
2018-08-20 09:51:25 -07:00
|
|
|
|
local repr_chunks = {}
|
|
|
|
|
for i, chunk in ipairs(chunks) do
|
|
|
|
|
local hl, text = unpack(chunk)
|
|
|
|
|
local attrs
|
2018-09-28 05:19:37 -07:00
|
|
|
|
if self._options.ext_linegrid then
|
2018-08-20 09:51:25 -07:00
|
|
|
|
attrs = self._attr_table[hl][1]
|
|
|
|
|
else
|
|
|
|
|
attrs = hl
|
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local attr_id = self:_get_attr_id(attr_state, attrs, hl)
|
2018-08-20 09:51:25 -07:00
|
|
|
|
repr_chunks[i] = {text, attr_id}
|
|
|
|
|
end
|
|
|
|
|
return repr_chunks
|
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
|
2017-01-19 17:20:34 -07:00
|
|
|
|
-- Generates tests. Call it where Screen:expect() would be. Waits briefly, then
|
|
|
|
|
-- dumps the current screen state in the form of Screen:expect().
|
|
|
|
|
-- Use snapshot_util({},true) to generate a text-only (no attributes) test.
|
|
|
|
|
--
|
|
|
|
|
-- @see Screen:redraw_debug()
|
2019-01-24 11:15:39 -07:00
|
|
|
|
function Screen:snapshot_util(attrs, ignore, request_cb)
|
|
|
|
|
self:sleep(250, request_cb)
|
2015-03-22 06:18:35 -07:00
|
|
|
|
self:print_snapshot(attrs, ignore)
|
|
|
|
|
end
|
|
|
|
|
|
2015-07-27 04:39:38 -07:00
|
|
|
|
function Screen:redraw_debug(attrs, ignore, timeout)
|
2015-03-22 06:18:35 -07:00
|
|
|
|
self:print_snapshot(attrs, ignore)
|
|
|
|
|
local function notification_cb(method, args)
|
|
|
|
|
assert(method == 'redraw')
|
|
|
|
|
for _, update in ipairs(args) do
|
2017-04-26 06:28:10 -07:00
|
|
|
|
-- mode_info_set is quite verbose, comment out the condition to debug it.
|
|
|
|
|
if update[1] ~= "mode_info_set" then
|
|
|
|
|
print(inspect(update))
|
|
|
|
|
end
|
2015-03-22 06:18:35 -07:00
|
|
|
|
end
|
|
|
|
|
self:_redraw(args)
|
|
|
|
|
self:print_snapshot(attrs, ignore)
|
|
|
|
|
return true
|
|
|
|
|
end
|
2015-07-27 04:39:38 -07:00
|
|
|
|
if timeout == nil then
|
|
|
|
|
timeout = 250
|
|
|
|
|
end
|
2017-12-09 03:26:06 -07:00
|
|
|
|
run_session(self._session, nil, notification_cb, nil, timeout)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:render(headers, attr_state, preview)
|
2017-04-26 06:28:10 -07:00
|
|
|
|
headers = headers and (self._options.ext_multigrid or self._options._debug_float)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local rv = {}
|
2023-09-13 16:42:22 -07:00
|
|
|
|
for igrid,grid in vim.spairs(self._grids) do
|
2017-12-09 03:26:06 -07:00
|
|
|
|
if headers then
|
2019-08-17 12:46:11 -07:00
|
|
|
|
local suffix = ""
|
2019-09-01 02:25:00 -07:00
|
|
|
|
if igrid > 1 and self.win_position[igrid] == nil
|
|
|
|
|
and self.float_pos[igrid] == nil and self.msg_grid ~= igrid then
|
2019-08-17 12:46:11 -07:00
|
|
|
|
suffix = " (hidden)"
|
|
|
|
|
end
|
|
|
|
|
table.insert(rv, "## grid "..igrid..suffix)
|
2017-12-09 03:26:06 -07:00
|
|
|
|
end
|
2019-09-01 02:25:00 -07:00
|
|
|
|
local height = grid.height
|
|
|
|
|
if igrid == self.msg_grid then
|
|
|
|
|
height = self._grids[1].height - self.msg_grid_pos
|
|
|
|
|
end
|
|
|
|
|
for i = 1, height do
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local cursor = self._cursor.grid == igrid and self._cursor.row == i
|
|
|
|
|
local prefix = (headers or preview) and " " or ""
|
2018-11-17 10:23:42 -07:00
|
|
|
|
table.insert(rv, prefix..self:_row_repr(igrid, i, attr_state, cursor).."|")
|
2017-12-09 03:26:06 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return rv
|
2015-03-22 06:18:35 -07:00
|
|
|
|
end
|
|
|
|
|
|
2019-07-20 05:01:33 -07:00
|
|
|
|
-- Returns the current screen state in the form of a screen:expect()
|
|
|
|
|
-- keyword-args map.
|
|
|
|
|
function Screen:get_snapshot(attrs, ignore)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
attrs = attrs or self._default_attr_ids
|
2015-01-17 06:59:47 -07:00
|
|
|
|
if ignore == nil then
|
|
|
|
|
ignore = self._default_attr_ignore
|
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local attr_state = {
|
|
|
|
|
ids = {},
|
|
|
|
|
ignore = ignore,
|
|
|
|
|
mutable = true, -- allow _row_repr to add missing highlights
|
|
|
|
|
}
|
2015-01-17 06:59:47 -07:00
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
if attrs ~= nil then
|
|
|
|
|
for i, a in pairs(attrs) do
|
|
|
|
|
attr_state.ids[i] = a
|
2015-01-17 04:46:29 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_linegrid then
|
|
|
|
|
attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
end
|
2015-01-17 04:46:29 -07:00
|
|
|
|
|
2017-12-09 03:26:06 -07:00
|
|
|
|
local lines = self:render(true, attr_state, true)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
|
|
|
|
|
local ext_state = self:_extstate_repr(attr_state)
|
|
|
|
|
for k, v in pairs(ext_state) do
|
|
|
|
|
if isempty(v) then
|
|
|
|
|
ext_state[k] = nil -- deleting keys while iterating is ok
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
2015-01-17 04:46:29 -07:00
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
|
2019-07-20 05:01:33 -07:00
|
|
|
|
-- Build keyword-args for screen:expect().
|
|
|
|
|
local kwargs = {}
|
|
|
|
|
if attr_state.modified then
|
|
|
|
|
kwargs['attr_ids'] = {}
|
|
|
|
|
for i, a in pairs(attr_state.ids) do
|
|
|
|
|
kwargs['attr_ids'][i] = a
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
kwargs['grid'] = table.concat(lines, '\n')
|
|
|
|
|
for _, k in ipairs(ext_keys) do
|
|
|
|
|
if ext_state[k] ~= nil then
|
|
|
|
|
kwargs[k] = ext_state[k]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-20 05:19:57 -07:00
|
|
|
|
return kwargs, ext_state, attr_state
|
2019-07-20 05:01:33 -07:00
|
|
|
|
end
|
|
|
|
|
|
2020-01-23 10:05:04 -07:00
|
|
|
|
local function fmt_ext_state(name, state)
|
2021-05-01 04:29:34 -07:00
|
|
|
|
local function remove_all_metatables(item, path)
|
|
|
|
|
if path[#path] ~= inspect.METATABLE then
|
|
|
|
|
return item
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-01-23 10:05:04 -07:00
|
|
|
|
if name == "win_viewport" then
|
|
|
|
|
local str = "{\n"
|
|
|
|
|
for k,v in pairs(state) do
|
|
|
|
|
str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = "
|
|
|
|
|
..v.topline..", botline = "..v.botline..", curline = "..v.curline
|
2023-03-13 12:38:21 -07:00
|
|
|
|
..", curcol = "..v.curcol..", linecount = "..v.linecount..", sum_scroll_delta = "..v.sum_scroll_delta.."};\n")
|
2020-01-23 10:05:04 -07:00
|
|
|
|
end
|
|
|
|
|
return str .. "}"
|
2021-05-01 04:29:34 -07:00
|
|
|
|
elseif name == "float_pos" then
|
|
|
|
|
local str = "{\n"
|
|
|
|
|
for k,v in pairs(state) do
|
|
|
|
|
str = str.." ["..k.."] = {{id = "..v[1].id.."}"
|
|
|
|
|
for i = 2, #v do
|
|
|
|
|
str = str..", "..inspect(v[i])
|
2020-01-23 10:05:04 -07:00
|
|
|
|
end
|
2021-05-01 04:29:34 -07:00
|
|
|
|
str = str .. "};\n"
|
2020-01-23 10:05:04 -07:00
|
|
|
|
end
|
2021-05-01 04:29:34 -07:00
|
|
|
|
return str .. "}"
|
|
|
|
|
else
|
|
|
|
|
-- TODO(bfredl): improve formatting of more states
|
2020-01-23 10:05:04 -07:00
|
|
|
|
return inspect(state,{process=remove_all_metatables})
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-20 05:01:33 -07:00
|
|
|
|
function Screen:print_snapshot(attrs, ignore)
|
2019-07-20 05:19:57 -07:00
|
|
|
|
local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local attrstr = ""
|
|
|
|
|
if attr_state.modified then
|
|
|
|
|
local attrstrs = {}
|
|
|
|
|
for i, a in pairs(attr_state.ids) do
|
|
|
|
|
local dict
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_linegrid then
|
|
|
|
|
dict = self:_pprint_hlitem(a)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
else
|
|
|
|
|
dict = "{"..self:_pprint_attrs(a).."}"
|
|
|
|
|
end
|
|
|
|
|
local keyval = (type(i) == "number") and "["..tostring(i).."]" or i
|
2020-09-08 00:47:10 -07:00
|
|
|
|
table.insert(attrstrs, " "..keyval.." = "..dict..";")
|
2018-08-31 06:56:53 -07:00
|
|
|
|
end
|
2019-07-20 05:19:57 -07:00
|
|
|
|
attrstr = (", attr_ids={\n"..table.concat(attrstrs, "\n").."\n}")
|
2018-08-31 06:56:53 -07:00
|
|
|
|
end
|
2019-09-01 02:25:00 -07:00
|
|
|
|
|
2019-07-20 05:19:57 -07:00
|
|
|
|
print( "\nscreen:expect{grid=[[")
|
2019-07-20 05:01:33 -07:00
|
|
|
|
print(kwargs.grid)
|
2018-08-31 06:56:53 -07:00
|
|
|
|
io.stdout:write( "]]"..attrstr)
|
|
|
|
|
for _, k in ipairs(ext_keys) do
|
2020-01-23 10:05:04 -07:00
|
|
|
|
if ext_state[k] ~= nil and not (k == "win_viewport" and not self.options.ext_multigrid) then
|
|
|
|
|
io.stdout:write(", "..k.."="..fmt_ext_state(k, ext_state[k]))
|
2018-08-31 06:56:53 -07:00
|
|
|
|
end
|
2015-01-17 06:59:47 -07:00
|
|
|
|
end
|
2019-07-20 05:19:57 -07:00
|
|
|
|
print("}\n")
|
2015-03-25 05:14:47 -07:00
|
|
|
|
io.stdout:flush()
|
2015-01-17 04:46:29 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
function Screen:_insert_hl_id(attr_state, hl_id)
|
|
|
|
|
if attr_state.id_to_index[hl_id] ~= nil then
|
|
|
|
|
return attr_state.id_to_index[hl_id]
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
local raw_info = self._hl_info[hl_id]
|
2019-10-11 10:27:15 -07:00
|
|
|
|
local info = nil
|
|
|
|
|
if self._options.ext_hlstate then
|
|
|
|
|
info = {}
|
|
|
|
|
if #raw_info > 1 then
|
|
|
|
|
for i, item in ipairs(raw_info) do
|
|
|
|
|
info[i] = self:_insert_hl_id(attr_state, item.id)
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
info[1] = {}
|
|
|
|
|
for k, v in pairs(raw_info[1]) do
|
|
|
|
|
if k ~= "id" then
|
|
|
|
|
info[1][k] = v
|
|
|
|
|
end
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local entry = self._attr_table[hl_id]
|
|
|
|
|
local attrval
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._rgb_cterm then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
attrval = {entry[1], entry[2], info} -- unpack() doesn't work
|
2019-10-11 10:27:15 -07:00
|
|
|
|
elseif self._options.ext_hlstate then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
attrval = {entry[1], info}
|
2019-10-11 10:27:15 -07:00
|
|
|
|
else
|
|
|
|
|
attrval = self._options.rgb and entry[1] or entry[2]
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
table.insert(attr_state.ids, attrval)
|
|
|
|
|
attr_state.id_to_index[hl_id] = #attr_state.ids
|
|
|
|
|
return #attr_state.ids
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
2019-10-11 10:27:15 -07:00
|
|
|
|
function Screen:linegrid_check_attrs(attrs)
|
2018-06-17 04:31:13 -07:00
|
|
|
|
local id_to_index = {}
|
2019-10-11 10:27:15 -07:00
|
|
|
|
for i, def_attr in pairs(self._attr_table) do
|
2018-06-17 04:31:13 -07:00
|
|
|
|
local iinfo = self._hl_info[i]
|
|
|
|
|
local matchinfo = {}
|
|
|
|
|
if #iinfo > 1 then
|
|
|
|
|
for k,item in ipairs(iinfo) do
|
|
|
|
|
matchinfo[k] = id_to_index[item.id]
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
matchinfo = iinfo
|
|
|
|
|
end
|
|
|
|
|
for k,v in pairs(attrs) do
|
|
|
|
|
local attr, info, attr_rgb, attr_cterm
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._rgb_cterm then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
attr_rgb, attr_cterm, info = unpack(v)
|
|
|
|
|
attr = {attr_rgb, attr_cterm}
|
2019-11-02 02:55:40 -07:00
|
|
|
|
info = info or {}
|
2019-10-11 10:27:15 -07:00
|
|
|
|
elseif self._options.ext_hlstate then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
attr, info = unpack(v)
|
2019-10-11 10:27:15 -07:00
|
|
|
|
else
|
|
|
|
|
attr = v
|
|
|
|
|
info = {}
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self:_equal_attr_def(attr, def_attr) then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
if #info == #matchinfo then
|
|
|
|
|
local match = false
|
|
|
|
|
if #info == 1 then
|
|
|
|
|
if self:_equal_info(info[1],matchinfo[1]) then
|
|
|
|
|
match = true
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
match = true
|
|
|
|
|
for j = 1,#info do
|
|
|
|
|
if info[j] ~= matchinfo[j] then
|
|
|
|
|
match = false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if match then
|
|
|
|
|
id_to_index[i] = k
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self:_equal_attr_def(self._rgb_cterm and {{}, {}} or {}, def_attr) and #self._hl_info[i] == 0 then
|
|
|
|
|
id_to_index[i] = ""
|
|
|
|
|
end
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
return id_to_index
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
2019-10-11 10:27:15 -07:00
|
|
|
|
function Screen:_pprint_hlitem(item)
|
2019-07-12 15:50:52 -07:00
|
|
|
|
-- print(inspect(item))
|
2019-10-11 10:27:15 -07:00
|
|
|
|
local multi = self._rgb_cterm or self._options.ext_hlstate
|
2019-11-02 02:55:40 -07:00
|
|
|
|
local cterm = (not self._rgb_cterm and not self._options.rgb)
|
|
|
|
|
local attrdict = "{"..self:_pprint_attrs(multi and item[1] or item, cterm).."}"
|
2018-06-17 04:31:13 -07:00
|
|
|
|
local attrdict2, hlinfo
|
2019-10-11 10:27:15 -07:00
|
|
|
|
local descdict = ""
|
|
|
|
|
if self._rgb_cterm then
|
2019-11-02 02:55:40 -07:00
|
|
|
|
attrdict2 = ", {"..self:_pprint_attrs(item[2], true).."}"
|
2018-06-17 04:31:13 -07:00
|
|
|
|
hlinfo = item[3]
|
|
|
|
|
else
|
|
|
|
|
attrdict2 = ""
|
|
|
|
|
hlinfo = item[2]
|
|
|
|
|
end
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_hlstate then
|
|
|
|
|
descdict = ", {"..self:_pprint_hlinfo(hlinfo).."}"
|
|
|
|
|
end
|
|
|
|
|
return (multi and "{" or "")..attrdict..attrdict2..descdict..(multi and "}" or "")
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_pprint_hlinfo(states)
|
|
|
|
|
if #states == 1 then
|
|
|
|
|
local items = {}
|
|
|
|
|
for f, v in pairs(states[1]) do
|
|
|
|
|
local desc = tostring(v)
|
|
|
|
|
if type(v) == type("") then
|
|
|
|
|
desc = '"'..desc..'"'
|
|
|
|
|
end
|
|
|
|
|
table.insert(items, f.." = "..desc)
|
|
|
|
|
end
|
|
|
|
|
return "{"..table.concat(items, ", ").."}"
|
|
|
|
|
else
|
|
|
|
|
return table.concat(states, ", ")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
2019-11-02 02:55:40 -07:00
|
|
|
|
function Screen:_pprint_attrs(attrs, cterm)
|
2015-01-23 15:52:48 -07:00
|
|
|
|
local items = {}
|
|
|
|
|
for f, v in pairs(attrs) do
|
2015-01-26 11:32:20 -07:00
|
|
|
|
local desc = tostring(v)
|
2016-04-24 02:46:48 -07:00
|
|
|
|
if f == "foreground" or f == "background" or f == "special" then
|
2015-03-17 04:45:13 -07:00
|
|
|
|
if Screen.colornames[v] ~= nil then
|
|
|
|
|
desc = "Screen.colors."..Screen.colornames[v]
|
2019-11-02 02:55:40 -07:00
|
|
|
|
elseif cterm then
|
|
|
|
|
desc = tostring(v)
|
2019-01-20 13:23:17 -07:00
|
|
|
|
else
|
|
|
|
|
desc = string.format("tonumber('0x%06x')",v)
|
2015-01-26 11:32:20 -07:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
table.insert(items, f.." = "..desc)
|
2015-01-23 15:52:48 -07:00
|
|
|
|
end
|
|
|
|
|
return table.concat(items, ", ")
|
|
|
|
|
end
|
2015-01-17 04:46:29 -07:00
|
|
|
|
|
2017-01-22 13:59:54 -07:00
|
|
|
|
local function backward_find_meaningful(tbl, from) -- luacheck: no unused
|
2014-12-08 18:31:45 -07:00
|
|
|
|
for i = from or #tbl, 1, -1 do
|
|
|
|
|
if tbl[i] ~= ' ' then
|
|
|
|
|
return i + 1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return from
|
|
|
|
|
end
|
|
|
|
|
|
2018-08-31 06:56:53 -07:00
|
|
|
|
function Screen:_get_attr_id(attr_state, attrs, hl_id)
|
|
|
|
|
if not attr_state.ids then
|
2014-12-08 18:31:45 -07:00
|
|
|
|
return
|
|
|
|
|
end
|
2018-07-06 05:39:50 -07:00
|
|
|
|
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._options.ext_linegrid then
|
2018-08-31 06:56:53 -07:00
|
|
|
|
local id = attr_state.id_to_index[hl_id]
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if id == "" then -- sentinel for empty it
|
|
|
|
|
return nil
|
|
|
|
|
elseif id ~= nil then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
return id
|
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
if attr_state.mutable then
|
|
|
|
|
id = self:_insert_hl_id(attr_state, hl_id)
|
|
|
|
|
attr_state.modified = true
|
|
|
|
|
return id
|
|
|
|
|
end
|
2022-05-02 12:10:01 -07:00
|
|
|
|
local kind = self._options.rgb and 1 or 2
|
|
|
|
|
return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][kind])
|
2018-06-17 04:31:13 -07:00
|
|
|
|
else
|
2019-10-13 00:19:57 -07:00
|
|
|
|
if self:_equal_attrs(attrs, {}) then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
-- ignore this attrs
|
|
|
|
|
return nil
|
|
|
|
|
end
|
2019-07-14 04:26:40 -07:00
|
|
|
|
for id, a in pairs(attr_state.ids) do
|
|
|
|
|
if self:_equal_attrs(a, attrs) then
|
|
|
|
|
return id
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-08-31 06:56:53 -07:00
|
|
|
|
if attr_state.mutable then
|
|
|
|
|
table.insert(attr_state.ids, attrs)
|
|
|
|
|
attr_state.modified = true
|
|
|
|
|
return #attr_state.ids
|
|
|
|
|
end
|
2018-06-17 04:31:13 -07:00
|
|
|
|
return "UNEXPECTED "..self:_pprint_attrs(attrs)
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
2018-06-17 04:31:13 -07:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Screen:_equal_attr_def(a, b)
|
2019-10-11 10:27:15 -07:00
|
|
|
|
if self._rgb_cterm then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2])
|
2019-10-11 10:27:15 -07:00
|
|
|
|
elseif self._options.rgb then
|
2018-06-17 04:31:13 -07:00
|
|
|
|
return self:_equal_attrs(a,b[1])
|
2019-10-11 10:27:15 -07:00
|
|
|
|
else
|
|
|
|
|
return self:_equal_attrs(a,b[2])
|
2015-01-17 06:59:47 -07:00
|
|
|
|
end
|
2014-12-08 18:31:45 -07:00
|
|
|
|
end
|
|
|
|
|
|
2015-11-17 15:31:22 -07:00
|
|
|
|
function Screen:_equal_attrs(a, b)
|
2015-01-17 04:46:29 -07:00
|
|
|
|
return a.bold == b.bold and a.standout == b.standout and
|
2022-06-30 01:57:44 -07:00
|
|
|
|
a.underline == b.underline and a.undercurl == b.undercurl and
|
|
|
|
|
a.underdouble == b.underdouble and a.underdotted == b.underdotted and
|
|
|
|
|
a.underdashed == b.underdashed and a.italic == b.italic and
|
2022-03-01 14:48:11 -07:00
|
|
|
|
a.reverse == b.reverse and a.foreground == b.foreground and
|
|
|
|
|
a.background == b.background and a.special == b.special and a.blend == b.blend and
|
2019-11-02 02:57:07 -07:00
|
|
|
|
a.strikethrough == b.strikethrough and
|
|
|
|
|
a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed
|
2015-01-17 04:46:29 -07:00
|
|
|
|
end
|
|
|
|
|
|
2018-06-17 04:31:13 -07:00
|
|
|
|
function Screen:_equal_info(a, b)
|
|
|
|
|
return a.kind == b.kind and a.hi_name == b.hi_name and
|
|
|
|
|
a.ui_name == b.ui_name
|
|
|
|
|
end
|
|
|
|
|
|
2015-11-17 15:31:22 -07:00
|
|
|
|
function Screen:_attr_index(attrs, attr)
|
2015-01-17 06:59:47 -07:00
|
|
|
|
if not attrs then
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
for i,a in pairs(attrs) do
|
2015-11-17 15:31:22 -07:00
|
|
|
|
if self:_equal_attrs(a, attr) then
|
2015-01-17 06:59:47 -07:00
|
|
|
|
return i
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
2014-12-08 18:31:45 -07:00
|
|
|
|
return Screen
|