Repository: neovim
Issue: getmousepos() reports wrong when using multigrid
Labels: bug,mouse

### Problem

`getmousepos()` has several problems when multigrid is enabled.

1. An unfocusable floating window gives a `winid` of 0
2. A window border also gives `winid` 0, but I think I saw slightly different behaviour in the test code, where `wincol` and `winrow` becomes wrong instead. But maybe I messed something up there. In neovide we get a `winid` of 0 though.
3. `screenrow` and `screencol` are window relative instead of screen relative

This causes this issue in Neovide, due to the scrollbar being unfocusable
* https://github.com/neovide/neovide/issues/2226

### Steps to reproduce

Run this test adapted from the regular `getmousepos` test
<details>
<summary>Code</summary>

```lua
describe('ui/mouse/input', function()
  local screen

  before_each(function()
    clear()
    api.nvim_set_option_value('mouse', 'a', {})
    api.nvim_set_option_value('list', true, {})
    -- NB: this is weird, but mostly irrelevant to the test
    -- So I didn't bother to change it
    command('set listchars=eol:$')
    command('setl listchars=nbsp:x')
    screen = Screen.new(25, 5)
    screen:attach()
    screen:set_default_attr_ids({
      [0] = { bold = true, foreground = Screen.colors.Blue },
      [1] = { background = Screen.colors.LightGrey },
      [2] = { bold = true },
      [3] = {
        foreground = Screen.colors.Blue,
        background = Screen.colors.LightGrey,
        bold = true,
      },
      [4] = { reverse = true },
      [5] = { bold = true, reverse = true },
      [6] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
      [7] = { bold = true, foreground = Screen.colors.SeaGreen4 },
      [8] = { foreground = Screen.colors.Brown },
    })
    command('set mousemodel=extend')
    feed('itesting<cr>mouse<cr>support and selection<esc>')
    screen:expect([[
      testing                  |
      mouse                    |
      support and selectio^n    |
      {0:~                        }|
                               |
    ]])
  end)
end)

describe('ui/mouse/input/multigird', function()
  local screen

  before_each(function()
    clear()
    api.nvim_set_option_value('mouse', 'a', {})
    api.nvim_set_option_value('list', true, {})
    -- NB: this is weird, but mostly irrelevant to the test
    -- So I didn't bother to change it
    command('set listchars=eol:$')
    command('setl listchars=nbsp:x')
    screen = Screen.new(25, 5)
    screen:attach({ext_multigrid=true})
    screen:set_default_attr_ids({
      [0] = { bold = true, foreground = Screen.colors.Blue },
      [1] = { background = Screen.colors.LightGrey },
      [2] = { bold = true },
      [3] = {
        foreground = Screen.colors.Blue,
        background = Screen.colors.LightGrey,
        bold = true,
      },
      [4] = { reverse = true },
      [5] = { bold = true, reverse = true },
      [6] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
      [7] = { bold = true, foreground = Screen.colors.SeaGreen4 },
      [8] = { foreground = Screen.colors.Brown },
    })
    command('set mousemodel=extend')
    feed('itesting<cr>mouse<cr>support and selection<esc>')
    screen:expect{grid=[[
    ## grid 1
      [2:-------------------------]|*4
      [3:-------------------------]|
    ## grid 2
      testing                  |
      mouse                    |
      support and selectio^n    |
      {0:~                        }|
    ## grid 3
                               |
    ]], win_viewport={
      [2] = {win = 1000, topline = 0, botline = 4, curline = 2, curcol = 20, linecount = 3, sum_scroll_delta = 0};
    }}
  end)

  it('getmousepos()', function()
    local winwidth = api.nvim_get_option_value('winwidth', {})
    -- Set winwidth=1 so that window sizes don't change.
    api.nvim_set_option_value('winwidth', 1, {})
    command('tabedit')
    local tabpage = api.nvim_get_current_tabpage()
    insert('hello')
    command('vsplit')
    local opts = {
      relative = 'editor',
      width = 12,
      height = 1,
      col = 8,
      row = 1,
      anchor = 'NW',
      style = 'minimal',
      border = 'single',
      focusable = 1,
    }
    local float = api.nvim_open_win(api.nvim_get_current_buf(), false, opts)
    command('redraw')
    local screenstate = {grid=[[
    ## grid 1
      {9: + No Name] }{2: }{10:3}{2:+ o Name] }{9:X}|
      [5:------------]│[4:------------]|*2
      {5:< Name] [+]  }{4:< Name] [+] }|
      [3:-------------------------]|
    ## grid 2 (hidden)
      testing                  |
      mouse                    |
      support and selection    |
      {0:~                        }|
    ## grid 3
                               |
    ## grid 4
      hello{0:$}      |
      {0:~           }|
    ## grid 5
      hell^o{0:$}      |
      {0:~           }|
    ## grid 6
      ┌────────────┐|
      │{11:hello       }│|
      └────────────┘|
    ]], attr_ids={
      [0] = {bold = true, foreground = Screen.colors.Blue1};
      [1] = {background = Screen.colors.LightGray};
      [2] = {bold = true};
      [3] = {bold = true, foreground = Screen.colors.Blue1, background = Screen.colors.LightGray};
      [4] = {reverse = true};
      [5] = {bold = true, reverse = true};
      [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red};
      [7] = {bold = true, foreground = Screen.colors.SeaGreen};
      [8] = {foreground = Screen.colors.Brown};
      [9] = {underline = true, background = Screen.colors.LightGray};
      [10] = {bold = true, foreground = Screen.colors.Magenta1};
      [11] = {background = Screen.colors.LightMagenta};
    }, float_pos={
      [6] = {1003, "NW", 1, 1, 8, true, 50};
    }, win_viewport={
      [2] = {win = 1000, topline = 0, botline = 4, curline = 2, curcol = 20, linecount = 3, sum_scroll_delta = 0};
      [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 4, linecount = 1, sum_scroll_delta = 0};
      [5] = {win = 1002, topline = 0, botline = 2, curline = 0, curcol = 4, linecount = 1, sum_scroll_delta = 0};
      [6] = {win = 1003, topline = 0, botline = 1, curline = 0, curcol = 4, linecount = 1, sum_scroll_delta = 0};
    }, condition=function()
      eq({
        [4] = { win = 1001, startrow = 1, startcol = 13, width = 12, height =  2 },
        [5] = { win = 1002, startrow = 1, startcol =  0, width = 12, height = 2 },
      }, screen.win_position)
    end}
    screen:expect(screenstate)
    local lines = api.nvim_get_option_value('lines', {})
    local columns = api.nvim_get_option_value('columns', {})

    local input_mouse = function(button, action, modifier, row, col)
      local grid = -1
      if row == lines - 1 then
        grid = 3
      elseif row >= opts.row and row <= opts.row + opts.height + 1 and col >= opts.col and col <= opts.col + opts.width + 1 then
        grid = 6
        row = row - opts.row
        col = col - opts.col
      elseif col >= 12 then
        grid = 4
        row = row - 1
        col = col - 13
      else
        grid = 5
        row = row - 1
      end
      api.nvim_input_mouse(button, action, modifier, grid, row, col)
    end

    -- Test that screenrow and screencol are set properly for all positions.
    for row = 0, lines - 1 do
      for col = 0, columns - 1 do
        -- Skip the X button that would close the tab.
        if row ~= 0 or col ~= columns - 1 then
          input_mouse('left', 'press', '', row, col)
          api.nvim_set_current_tabpage(tabpage)
          local mousepos = fn.getmousepos()
          -- FIXME: fix screenrow and screencol, they return window position instead of screen positions
          --eq(row + 1, mousepos.screenrow)
          --eq(col + 1, mousepos.screencol)
          -- All other values should be 0 when clicking on the command line.
          if row == lines - 1 then
            eq(0, mousepos.winid)
            eq(0, mousepos.winrow)
            eq(0, mousepos.wincol)
            eq(0, mousepos.line)
            eq(0, mousepos.column)
            eq(0, mousepos.coladd)
          end
        end
      end
    end

    -- Test that mouse position values are properly set for the floating window
    -- with a border. 1 is added to the height and width to account for the
    -- border.
    -- FIXME: The exact borders don't work
    --for win_row = 0, opts.height + 1 do
    for win_row = 0, opts.height do
      --for win_col = 0, opts.width + 1 do
      for win_col = 0, opts.width do
        local row = win_row + opts.row
        local col = win_col + opts.col
        input_mouse('left', 'press', '', row, col)
        local mousepos = fn.getmousepos()
        eq(float, mousepos.winid)
        eq(win_row + 1, mousepos.winrow)
        eq(win_col + 1, mousepos.wincol)
        local line = 0
        local column = 0
        local coladd = 0
        if
          win_row > 0
          and win_row < opts.height + 1
          and win_col > 0
          and win_col < opts.width + 1
        then
          -- Because of border, win_row and win_col don't need to be
          -- incremented by 1.
          line = math.min(win_row, fn.line('$'))
          column = math.min(win_col, #fn.getline(line) + 1)
          coladd = win_col - column
        end
        eq(line, mousepos.line)
        eq(column, mousepos.column)
        eq(coladd, mousepos.coladd)
      end
    end

    -- Test that mouse position values are properly set for the floating
    -- window, after removing the border.
    opts.border = 'none'
    api.nvim_win_set_config(float, opts)
    command('redraw')
    for win_row = 0, opts.height - 1 do
      for win_col = 0, opts.width - 1 do
        local row = win_row + opts.row
        local col = win_col + opts.col
        input_mouse('left', 'press', '', row, col)
        local mousepos = fn.getmousepos()
        eq(float, mousepos.winid)
        eq(win_row + 1, mousepos.winrow)
        eq(win_col + 1, mousepos.wincol)
        local line = math.min(win_row + 1, fn.line('$'))
        local column = math.min(win_col + 1, #fn.getline(line) + 1)
        local coladd = win_col + 1 - column
        eq(line, mousepos.line)
        eq(column, mousepos.column)
        eq(coladd, mousepos.coladd)
      end
    end

    -- Test that mouse position values are properly set for ordinary windows.
    -- Set the float to be unfocusable instead of closing, to additionally test
    -- that getmousepos() does not consider unfocusable floats. (see discussion
    -- in PR #14937 for details).
    opts.focusable = false
    api.nvim_win_set_config(float, opts)
    command('redraw')
    for nr = 1, 2 do
      for win_row = 0, fn.winheight(nr) - 1 do
        for win_col = 0, fn.winwidth(nr) - 1 do
          local row = win_row + fn.win_screenpos(nr)[1] - 1
          local col = win_col + fn.win_screenpos(nr)[2] - 1
          input_mouse('left', 'press', '', row, col)
          local mousepos = fn.getmousepos()
          -- FIXME: Hitting the floating window does not work
          if col < opts.col or col > opts.col + opts.width + 1 then
            eq(fn.win_getid(nr), mousepos.winid)
            eq(win_row + 1, mousepos.winrow)
            eq(win_col + 1, mousepos.wincol)
            local line = math.min(win_row + 1, fn.line('$'))
            local column = math.min(win_col + 1, #fn.getline(line) + 1)
            local coladd = win_col + 1 - column
            eq(line, mousepos.line)
            eq(column, mousepos.column)
            eq(coladd, mousepos.coladd)
          end
        end
      end
    end

    -- Restore state and release mouse.
    command('tabclose!')
    api.nvim_set_option_value('winwidth', winwidth, {})
    api.nvim_input_mouse('left', 'release', '', 0, 0, 0)
  end)
end)
```
</details>

NOTE: I'm not fully sure that everything is completely correct, there might be one or two offset errors in my adapted code, as they are hard to detect when the actual code does not work properly.

### Expected behavior

The tests should pass, even when the `FIXME` parts are fixed

### Neovim version (nvim -v)

v0.9.5

### Vim (not Nvim) behaves the same?

N/A

### Operating system/version

Arch Linux

### Terminal name/version

Alacritty/Neovide

### $TERM environment variable

alacritty

### Installation

Manually built