ronantakizawa/codereview-bench huggingface dataset:
Repository: neovim/neovim
File: src/gen/gen_api_dispatch.lua (Lua)

Code before:
      goto exit_%u;
    }

    ]], #fn.parameters - j + errshift)
    free_code[#free_code + 1] = arg_free_code
    cparams = cparam .. ', ' .. cparams
  end
  if fn.receives_channel_id then
    --- @type string
    cparams = 'LUA_INTERNAL_CALL, ' .. cparams
  end
  if fn.receives_arena then
    cparams = cparams .. '&arena, '
  end

  if fn.has_lua_imp then
    cparams = cparams .. 'lstate, '
  end

  if fn.can_fail then
    cparams = cparams .. '&err'
  else
    cparams = cparams:gsub(', $', '')
  end

  write_shifted_output('  ENTER_LUA_ACTIVE_STATE(lstate);\n')
  local free_at_exit_code = ''
  for i = 1, #free_code do
    local rev_i = #free_code - i + 1
    local code = free_code[rev_i]
    if i == 1 and not real_type(fn.parameters[1][1]):match('^KeyDict_') then
      free_at_exit_code = free_at_exit_code .. ('\n%s'):format(code)
    else
      free_at_exit_code = free_at_exit_code .. ('\nexit_%u:\n%s'):format(rev_i, code)
    end
  end
  local err_throw_code = [[

exit_0:
  arena_mem_free(arena_finish(&arena));
  if (ERROR_SET(&err)) {
    luaL_where(lstate, 1);
    if (err_param) {
      lua_pushstring(lstate, "Invalid '");
      lua_pushstring(lstate, err_param);
      lua_pushstring(lstate, "': ");
    }
    lua_pushstring(lstate, err.msg);
    api_clear_error(&err);
    lua_concat(lstate, err_param ? 5 : 2);
    return lua_error(lstate);

Review comment (suggestion):
`write_shifted_output()` reduces indent by 2, so the indent should be 4 spaces here.
```suggestion
  write_shifted_output('    ENTER_LUA_ACTIVE_STATE(lstate);\n')
```

Code after:
      goto exit_%u;
    }

    ]], #fn.parameters - j + errshift)
    free_code[#free_code + 1] = arg_free_code
    cparams = cparam .. ', ' .. cparams
  end
  if fn.receives_channel_id then
    --- @type string
    cparams = 'LUA_INTERNAL_CALL, ' .. cparams
  end
  if fn.receives_arena then
    cparams = cparams .. '&arena, '
  end

  if fn.has_lua_imp then
    cparams = cparams .. 'lstate, '
  end

  if fn.can_fail then
    cparams = cparams .. '&err'
  else
    cparams = cparams:gsub(', $', '')
  end

  write_shifted_output('    ENTER_LUA_ACTIVE_STATE(lstate);\n')
  local free_at_exit_code = ''
  for i = 1, #free_code do
    local rev_i = #free_code - i + 1
    local code = free_code[rev_i]
    if i == 1 and not real_type(fn.parameters[1][1]):match('^KeyDict_') then
      free_at_exit_code = free_at_exit_code .. ('\n%s'):format(code)
    else
      free_at_exit_code = free_at_exit_code .. ('\nexit_%u:\n%s'):format(rev_i, code)
    end
  end
  local err_throw_code = [[

exit_0:
  arena_mem_free(arena_finish(&arena));
  if (ERROR_SET(&err)) {
    luaL_where(lstate, 1);
    if (err_param) {
      lua_pushstring(lstate, "Invalid '");
      lua_pushstring(lstate, err_param);
      lua_pushstring(lstate, "': ");
    }
    lua_pushstring(lstate, err.msg);
    api_clear_error(&err);
    lua_concat(lstate, err_param ? 5 : 2);
    return lua_error(lstate);
Repository: neovim/neovim
File: src/gen/gen_api_dispatch.lua (Lua)

Code before:
      -- only push onto the Lua stack if we haven't already
      write_shifted_output(
        [[
    if (lua_gettop(lstate) == 0) {
      nlua_push_%s(lstate, %sret, kNluaPushSpecial | kNluaPushFreeRefs);
    }
      ]],
        return_type,
        ret_mode
      )
    elseif ret_type:match('^KeyDict_') then
      write_shifted_output('    nlua_push_keydict(lstate, &ret, %s_table);\n', return_type:sub(9))
    else
      local special = (fn.since ~= nil and fn.since < 11)
      write_shifted_output(
        '    nlua_push_%s(lstate, %sret, %s | kNluaPushFreeRefs);\n',
        return_type,
        ret_mode,
        special and 'kNluaPushSpecial' or '0'
      )
    end

    -- NOTE: we currently assume err_throw needs nothing from arena
    write_shifted_output(
      [[
      LEAVE_LUA_ACTIVE_STATE();
  %s
  %s
  %s
    return 1;
    ]],
      free_retval,
      free_at_exit_code,
      err_throw_code
    )
  else
    write_shifted_output(
      [[
    %s(%s);
    LEAVE_LUA_ACTIVE_STATE();
  %s
  %s
    return 0;
    ]],
      fn.name,
      cparams,
      free_at_exit_code,
      err_throw_code
    )
  end
  write_shifted_output([[

Review comment (suggestion):
Same here.
```suggestion
    LEAVE_LUA_ACTIVE_STATE();
```

Code after:
      -- only push onto the Lua stack if we haven't already
      write_shifted_output(
        [[
    if (lua_gettop(lstate) == 0) {
      nlua_push_%s(lstate, %sret, kNluaPushSpecial | kNluaPushFreeRefs);
    }
      ]],
        return_type,
        ret_mode
      )
    elseif ret_type:match('^KeyDict_') then
      write_shifted_output('    nlua_push_keydict(lstate, &ret, %s_table);\n', return_type:sub(9))
    else
      local special = (fn.since ~= nil and fn.since < 11)
      write_shifted_output(
        '    nlua_push_%s(lstate, %sret, %s | kNluaPushFreeRefs);\n',
        return_type,
        ret_mode,
        special and 'kNluaPushSpecial' or '0'
      )
    end

    -- NOTE: we currently assume err_throw needs nothing from arena
    write_shifted_output(
      [[
    LEAVE_LUA_ACTIVE_STATE();
  %s
  %s
  %s
    return 1;
    ]],
      free_retval,
      free_at_exit_code,
      err_throw_code
    )
  else
    write_shifted_output(
      [[
    %s(%s);
    LEAVE_LUA_ACTIVE_STATE();
  %s
  %s
    return 0;
    ]],
      fn.name,
      cparams,
      free_at_exit_code,
      err_throw_code
    )
  end
  write_shifted_output([[
Repository: neovim/neovim
File: test/functional/plugin/lsp/completion_spec.lua (Lua)

Code before:
          },
          insertTextFormat = 2,
          data = 'foobar',
        },
        items = {
          {
            label = 'hello',
            data = 'item-property-has-priority',
          },
        },
      }
      local result = complete('|', completion_list)
      eq(1, #result.items)
      local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText
      eq('hello', text)
    end
  )

  it('prepends prefix for items with different start positions', function()
    local text = 'div.foo|'

    local item_emmet = {
      label = 'div.foo',
      insertTextFormat = 1,
      textEdit = {
        newText = '<div class="foo"></div>',
        range = { start = { line = 0, character = 0 }, ['end'] = { line = 0, character = 7 } },
      },
    }

    local item_regular = {
      label = 'foobar',
      textEdit = {
        newText = 'foobar',
        range = { start = { line = 0, character = 4 }, ['end'] = { line = 0, character = 7 } },
      },
    }

    local result = complete(text, { items = { item_emmet, item_regular } })

    eq(0, result.server_start_boundary)

    for _, item in ipairs(result.items) do
      if item.abbr == 'foobar' then
        eq('div.foobar', item.word)
      end
    end
  end)

  it('uses the start boundary from an insertReplace response', function()
    local completion_list = {

Review comment (suggestion):
It would be best to include the expanded test results and cursor position (sould be in there right ? `...>|<..` 🤔) , as I provided above.

Code after:
          },
          insertTextFormat = 2,
          data = 'foobar',
        },
        items = {
          {
            label = 'hello',
            data = 'item-property-has-priority',
          },
        },
      }
      local result = complete('|', completion_list)
      eq(1, #result.items)
      local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText
      eq('hello', text)
    end
  )

  it('uses the start boundary from an insertReplace response', function()
    local completion_list = {
      isIncomplete = false,
      items = {
        {
          data = { cacheId = 1 },
          kind = 2,
          label = 'foobar',
          sortText = '11',
          textEdit = {
            insert = {
              start = { character = 4, line = 4 },
              ['end'] = { character = 8, line = 4 },
            },
            newText = 'foobar',
            replace = {
              start = { character = 4, line = 4 },
              ['end'] = { character = 8, line = 4 },
            },
          },
        },
        {
          data = { cacheId = 2 },
          kind = 2,
          label = 'bazqux',
          sortText = '11',
          textEdit = {
            insert = {
              start = { character = 4, line = 4 },
              ['end'] = { character = 5, line = 4 },
            },
            newText = 'bazqux',
            replace = {
Repository: neovim/neovim
File: runtime/lua/vim/iter.lua (Lua)

Code before:
  return self
end

--- Gets the next value from the iterator without consuming it
---
--- works for all iterator types (list iterators, function iterators, pairs(), etc)
---
--- The value returned by |Iter:peek()| will be returned again by the next call
--- to |Iter:next()|
---
--- Example:
---
--- ```lua
---
--- local it = vim.iter({ 3, 6, 9, 12 })
--- it:peek()
--- -- 3
--- it:peek()
--- -- 3
--- it:next()
--- -- 3
---
--- ```
---
---@return any
function Iter:peek()
  if not self._peeked then
    self._peeked = pack(self._next(self))
  end

  return unpack(self._peeked)
end

---@private
function ArrayIter:peek()
  if self._head == self._tail then
    return nil
  end

  if not self._peeked then
    self._peeked = self._table[self._head]
  end

  return unpack(self._peeked)
end

--- Find the first value in the iterator that satisfies the given predicate.
---
--- Advances the iterator. Returns nil and drains the iterator if no value is found.
---
--- Examples:

Review comment (suggestion):
I know this PR is still a draft, but here's some feedback. Does `ArrayIter:peek()` need to be updated? It looks like `._peeked` is only necessary for `Iter`.

Code after:
---
--- Example:
---
--- ```lua
---
--- local it = vim.iter({ 3, 6, 9, 12 })
--- it:peek()
--- -- 3
--- it:peek()
--- -- 3
--- it:next()
--- -- 3
---
--- ```
---
---@return any
function Iter:peek()
  if not self._peeked then
    self._peeked = pack(self:next())
  end

  return unpack(self._peeked)
end

---@private
function ArrayIter:peek()
  if self._head ~= self._tail then
    return self._table[self._head]
  end
end

--- Find the first value in the iterator that satisfies the given predicate.
---
--- Advances the iterator. Returns nil and drains the iterator if no value is found.
---
--- Examples:
---
--- ```lua
---
--- local it = vim.iter({ 3, 6, 9, 12 })
--- it:find(12)
--- -- 12
---
--- local it = vim.iter({ 3, 6, 9, 12 })
--- it:find(20)
--- -- nil
---
--- local it = vim.iter({ 3, 6, 9, 12 })
--- it:find(function(v) return v % 4 == 0 end)
--- -- 12
---
Repository: neovim/neovim
File: runtime/lua/vim/iter.lua (Lua)

Code before:

---@nodoc
function ArrayIter:rpeek()
  if self._head ~= self._tail then
    local inc = self._head < self._tail and 1 or -1
    return self._table[self._tail - inc]
  end
end

--- Skips `n` values of an iterator pipeline, or skips values while a predicate returns true
---
--- When a predicate is used, skipping stops at the first value for which the
--- predicate returns false. That value is not consumed and will be returned
--- by the next call to |Iter:next()|
---
--- Example:
---
--- ```lua
---
--- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
--- it:next()
--- -- 9
---
--- local function pred(x) return x < 10 end
--- local it2 = vim.iter({ 3, 6, 9, 12 }):skip(pred)
--- it2:next()
--- -- 12
--- ```
---
---@param n integer|fun(...):boolean Number of values to skip or a predicate.
---@return Iter
function Iter:skip(n)
  if type(n) == 'number' then
    for _ = 1, n do
      self._peeked = nil
      local _ = self:next()
    end
  elseif type(n) == 'function' then
    local raw_next = self.next

    self.next = function()
      while true do
        if self._peeked ~= nil then
          local a, b, c = unpack(self._peeked)
          if not n(a, b, c) then
            self._peeked = nil
            return a, b, c
          end

          self._peeked = nil
        end

Review comment (performance):
One more thing: see if you can implement `Iter:skip(predicate)` efficiently without using `._peeked` and just by using the public `:peek()` api. That'll prevent some code reuse where `:skip()` interacts with the same state as `:peek()`.

Code after:
  end
end

--- Skips `n` values of an iterator pipeline, or skips values while a predicate returns |lua-truthy|.
---
--- When a predicate is used, skipping stops at the first value for which the
--- predicate returns non-truthy. That value is not consumed and will be returned
--- by the next call to |Iter:next()|
---
--- Example:
---
--- ```lua
---
--- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
--- it:next()
--- -- 9
---
--- local function pred(x) return x < 10 end
--- local it2 = vim.iter({ 3, 6, 9, 12 }):skip(pred)
--- it2:next()
--- -- 12
--- ```
---
---@param n integer|fun(...):boolean Number of values to skip or a predicate.
---@return Iter
function Iter:skip(n)
  if type(n) == 'number' then
    for _ = 1, n do
      self._peeked = nil
      local _ = self:next()
    end
  elseif type(n) == 'function' then
    local next = self.next

    self.next = function()
      while true do
        local peeked = self._peeked or pack(next(self))

        if not peeked then
          return nil
        end

        if not n(unpack(peeked)) then
          self._peeked = nil
          return unpack(peeked)
        end

        self._peeked = nil
      end
    end
  end
Repository: neovim/neovim
File: runtime/lua/vim/iter.lua (Lua)

Code before:
--- it:next()
--- -- 9
---
--- local function pred(x) return x < 10 end
--- local it2 = vim.iter({ 3, 6, 9, 12 }):skip(pred)
--- it2:next()
--- -- 12
--- ```
---
---@param n integer|fun(...):boolean Number of values to skip or a predicate.
---@return Iter
function Iter:skip(n)
  if type(n) == 'number' then
    for _ = 1, n do
      self._peeked = nil
      local _ = self:next()
    end
  elseif type(n) == 'function' then
    local next = self.next

    self.next = function()
      while true do
        local peeked = self._peeked
        if not peeked then
          peeked = pack(next(self))
        end

        if not peeked then
          return nil
        end

        if not n(unpack(peeked)) then
          self._peeked = nil
          return unpack(peeked)
        end

        self._peeked = nil
      end
    end
  end
  return self
end

---@private
function ArrayIter:skip(n)
  if type(n) == 'function' then
    while self._head ~= self._tail do
      local v = self._table[self._head]
      if not n(unpack(v)) then
        break
      end

Review comment (suggestion):
```suggestion
        local peeked = self._peeked or pack(next(self))
```

Code after:
--- it:next()
--- -- 9
---
--- local function pred(x) return x < 10 end
--- local it2 = vim.iter({ 3, 6, 9, 12 }):skip(pred)
--- it2:next()
--- -- 12
--- ```
---
---@param n integer|fun(...):boolean Number of values to skip or a predicate.
---@return Iter
function Iter:skip(n)
  if type(n) == 'number' then
    for _ = 1, n do
      self._peeked = nil
      local _ = self:next()
    end
  elseif type(n) == 'function' then
    local next = self.next

    self.next = function()
      while true do
        local peeked = self._peeked or pack(next(self))

        if not peeked then
          return nil
        end

        if not n(unpack(peeked)) then
          self._peeked = nil
          return unpack(peeked)
        end

        self._peeked = nil
      end
    end
  end
  return self
end

---@private
function ArrayIter:skip(n)
  if type(n) == 'function' then
    while self._head ~= self._tail do
      local v = self._table[self._head]
      if not n(unpack(v)) then
        break
      end

      self._head = self._head + (self._head < self._tail and 1 or -1)
    end
Repository: neovim/neovim
File: test/functional/testnvim.lua (Lua)

Code before:
  M.nvim_argv = new_nvim_argv
  M.prepend_argv = prepend_argv
end

local session --- @type test.Session?
local loop_running --- @type boolean?
local last_error --- @type string?
local method_error --- @type string?

if not is_os('win') then
  local sigpipe_handler = assert(uv.new_signal())
  uv.signal_start(sigpipe_handler, 'sigpipe', function()
    print('warning: got SIGPIPE signal. Likely related to a crash in nvim')
  end)
end

function M.get_session()
  return session
end

function M.set_session(s)
  session = s
end

--- Clear path exclusions from 'shada' that interfere with tests running in /tmp/
function M.clear_shada_path_exclusions()
  local shada = M.api.nvim_get_option_value('shada', {})
  shada = shada:gsub(',r/tmp/', ''):gsub(',r/private/', '') --[[@as string]]
  M.api.nvim_set_option_value('shada', shada, {})
end

--- @param method string
--- @param ... any
--- @return any
function M.request(method, ...)
  assert(session, 'no Nvim session')
  assert(not session.eof_err, 'sending request after EOF from Nvim')
  local status, rv = session:request(method, ...)
  if not status then
    if loop_running then
      --- @type string
      last_error = rv[2]
      session:stop()
    else
      error(rv[2])
    end
  end
  return rv
end

--- @param method string

Review comment (suggestion):
Instead of calling this everywhere can you just update the `set` call here: https://github.com/neovim/neovim/blob/ed4c549ea2436b820860c3453d348c84d441de9b/test/functional/testnvim.lua#L26-L27

The `is able to set all& after setting &shada` test in `test/functional/shada/shada_spec.lua` should still work correctly since `:set all&` will reset to the internal default.

Code after:
  M.nvim_argv = new_nvim_argv
  M.prepend_argv = prepend_argv
end

local session --- @type test.Session?
local loop_running --- @type boolean?
local last_error --- @type string?
local method_error --- @type string?

if not is_os('win') then
  local sigpipe_handler = assert(uv.new_signal())
  uv.signal_start(sigpipe_handler, 'sigpipe', function()
    print('warning: got SIGPIPE signal. Likely related to a crash in nvim')
  end)
end

function M.get_session()
  return session
end

function M.set_session(s)
  session = s
end

--- @param method string
--- @param ... any
--- @return any
function M.request(method, ...)
  assert(session, 'no Nvim session')
  assert(not session.eof_err, 'sending request after EOF from Nvim')
  local status, rv = session:request(method, ...)
  if not status then
    if loop_running then
      --- @type string
      last_error = rv[2]
      session:stop()
    else
      error(rv[2])
    end
  end
  return rv
end

--- @param method string
--- @param ... any
--- @return any
function M.request_lua(method, ...)
  return M.exec_lua([[return vim.api[...](select(2, ...))]], method, ...)
end

--- @param timeout? integer
Repository: neovim/neovim
File: test/functional/ex_cmds/help_spec.lua (Lua)

Code before:
local t = require('test.testutil')
local n = require('test.functional.testnvim')()

local clear = n.clear
local command = n.command
local eq = t.eq
local fn = n.fn
local api = n.api
local mkdir = t.mkdir
local rmdir = n.rmdir
local write_file = t.write_file

describe(':help', function()
  before_each(clear)

  it('{subject}', function()
    n.command('helptags ++t $VIMRUNTIME/doc')
    local function check_tag(cmd, tag)
      local cmd_ok = t.pcall(n.command, cmd)
      local found = n.api.nvim_get_current_line():find(tag, 1, true)
      local errmsg = (not cmd_ok and 'command failed') or (not found and 'tag not found')
      assert(
        cmd_ok and found,
        string.format('Expected `:%s` to jump to tag `%s`, but %s', cmd, tag, errmsg)
      )
      n.command('helpclose')
    end

    check_tag('help', '*help.txt*')
    check_tag('help |', '*bar*')
    check_tag('help "*', '*quotestar*')
    check_tag('help ch??khealth', '*:checkhealth*')

    check_tag([[help \\star]], [[*/\star*]])
    check_tag('help /*', [[*/\star*]])
    check_tag('help ?', '*?*')
    check_tag('help ??', '*??*')
    check_tag('help expr-!=?', '*expr-!=?*')

    check_tag('help /<cr>', '*/<CR>*')
    check_tag([[help %(\\)]], [[*/\%(\)*]])
    check_tag('help %^', [[/\%^]])
    check_tag('help /_^G', '/_CTRL-G')
    check_tag([[help \0]], [[\0]])

    check_tag('help !', '*!*')

Review comment (bug):
ensure string

```suggestion
      local errmsg = (not cmd_ok and 'command failed') or (not found and 'tag not found') or '?'
```

Code after:
local t = require('test.testutil')
local n = require('test.functional.testnvim')()

local clear = n.clear
local command = n.command
local eq = t.eq
local fn = n.fn
local api = n.api
local mkdir = t.mkdir
local rmdir = n.rmdir
local write_file = t.write_file

describe(':help', function()
  before_each(clear)

  it('{subject}', function()
    n.command('helptags ++t $VIMRUNTIME/doc')
    local function check_tag(cmd, tag)
      local cmd_ok = t.pcall(n.command, cmd)
      local found = n.api.nvim_get_current_line():find(tag, 1, true)
      local errmsg = (not cmd_ok and 'command failed') or (not found and 'tag not found') or '?'
      assert(
        cmd_ok and found,
        string.format('Expected `:%s` to jump to tag `%s`, but %s', cmd, tag, errmsg)
      )
      n.command('helpclose')
    end

    check_tag('help', '*help.txt*')
    check_tag('help |', '*bar*')
    check_tag('help "*', '*quotestar*')
    check_tag('help ch??khealth', '*:checkhealth*')

    check_tag([[help \\star]], [[*/\star*]])
    check_tag('help /*', [[*/\star*]])
    check_tag('help ?', '*?*')
    check_tag('help ??', '*??*')
    check_tag('help expr-!=?', '*expr-!=?*')

    check_tag('help /<cr>', '*/<CR>*')
    check_tag([[help %(\\)]], [[*/\%(\)*]])
    check_tag('help %^', [[/\%^]])
    check_tag('help /_^G', '/_CTRL-G')
    check_tag([[help \0]], [[\0]])

    check_tag('help !', '*!*')
Repository: neovim/neovim
File: src/nvim/os/pty_proc_unix.c (C)

Code before:
  return 0;

error:
  close(master);
  kill(pid, SIGKILL);
  waitpid(pid, NULL, 0);
  return status;
}

const char *pty_proc_tty_name(PtyProc *ptyproc)
{
  return ptsname(ptyproc->tty_fd);
}

void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
  FUNC_ATTR_NONNULL_ALL
{
  ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
  ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
}

void pty_proc_resume(PtyProc *ptyproc)
{
  kill(((Proc *)ptyproc)->pid, SIGCONT);
}

void pty_proc_close(PtyProc *ptyproc)
  FUNC_ATTR_NONNULL_ALL
{
  pty_proc_close_master(ptyproc);
  Proc *proc = (Proc *)ptyproc;
  if (proc->internal_close_cb) {
    proc->internal_close_cb(proc);
  }
}

void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
{
  if (ptyproc->tty_fd >= 0) {
    close(ptyproc->tty_fd);
    ptyproc->tty_fd = -1;
  }
}

void pty_proc_teardown(Loop *loop)
{
  uv_signal_stop(&loop->children_watcher);
}

static void init_child(PtyProc *ptyproc)
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NORETURN

Review comment (suggestion):
probably doesn't matter, but elsewhere we use `uv_kill` with negative PID to signal the progress group: https://github.com/neovim/neovim/blob/3fd8a8b0e70c2a1aa2ee2d263629196888e0edf3/src/nvim/os/proc.c#L104

Code after:
  return 0;

error:
  close(master);
  kill(pid, SIGKILL);
  waitpid(pid, NULL, 0);
  return status;
}

const char *pty_proc_tty_name(PtyProc *ptyproc)
{
  return ptsname(ptyproc->tty_fd);
}

void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
  FUNC_ATTR_NONNULL_ALL
{
  ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
  ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
}

void pty_proc_resume(PtyProc *ptyproc)
{
  // Send SIGCONT to the entire process group, as some shells (e.g. fish) don't
  // propagate SIGCONT to suspended child processes.
  killpg(((Proc *)ptyproc)->pid, SIGCONT);
}

void pty_proc_close(PtyProc *ptyproc)
  FUNC_ATTR_NONNULL_ALL
{
  pty_proc_close_master(ptyproc);
  Proc *proc = (Proc *)ptyproc;
  if (proc->internal_close_cb) {
    proc->internal_close_cb(proc);
  }
}

void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
{
  if (ptyproc->tty_fd >= 0) {
    close(ptyproc->tty_fd);
    ptyproc->tty_fd = -1;
  }
}

void pty_proc_teardown(Loop *loop)
{
  uv_signal_stop(&loop->children_watcher);
}