ronantakizawa/codereview-bench huggingface dataset:
Repository: neovim/neovim
File: test/functional/legacy/prompt_buffer_spec.lua (Lua)

Code before:
      {1:~                        }|*3
                               |
    ]])

    feed('Fdx')
    screen:expect([[
      cmd: tests-mid^le-initial |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
                               |
    ]])

    -- can undo edits until prompt has been submitted
    feed('u')
    screen:expect([[
      cmd: tests-mid^dle-initial|
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      1 change; {MATCH:.*} |
    ]])

    -- undo is reflected in nvim_getinput
    eq('tests-middle-initial', fn('prompt_getinput', buf))

    feed('u')
    screen:expect([[
      cmd: tests-^initial       |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      1 change; {MATCH:.*} |
    ]])

    feed('i<cr><esc>')
    screen:expect([[
      cmd: tests-initial       |
      Command: "tests-initial" |
      Result: "tests-initial"  |
      cmd:^                     |
      {3:[Prompt]                 }|
      other buffer             |
      {1:~                        }|*3
                               |
    ]])

    -- after submit undo does nothing

Review comment (suggestion):
```suggestion
    -- undo is reflected in nvim_getinput
```
```suggestion
    -- undo is reflected in prompt_getinput
```

Code after:
      {5:-- INSERT --}             |
    ]])
  end)

  it('can undo current prompt', function()
    source_script()
    local buf = api.nvim_get_current_buf()

    -- text editiing alowed in current prompt
    feed('tests-initial<esc>')
    feed('bimiddle-<esc>')
    screen:expect([[
      cmd: tests-middle^-initial|
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
                               |
    ]])

    feed('Fdx')
    screen:expect([[
      cmd: tests-mid^le-initial |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
                               |
    ]])

    -- can undo edits until prompt has been submitted
    feed('u')
    screen:expect([[
      cmd: tests-mid^dle-initial|
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      1 change; {MATCH:.*} |
    ]])

    -- undo is reflected in prompt_getinput
    eq('tests-middle-initial', fn('prompt_getinput', buf))

    feed('u')
    screen:expect([[
      cmd: tests-^initial       |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
Repository: neovim/neovim
File: test/functional/legacy/prompt_buffer_spec.lua (Lua)

Code before:

    feed('<esc>k0Oat prompt')
    screen:expect([[
      after                    |
      line 3"                  |
      cmd: at prompt^           |
      line 4                   |
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<cr>')
    screen:expect([[
      line 4                   |
      after prompt             |
      line 5"                  |
      cmd: ^                    |
      {3:[Prompt]                 }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    -- newline created with o/O is reflected in nvim_getinput
    feed('asdf<esc>o<esc>')
    eq('asdf\n', fn('prompt_getinput', buf))
    feed('kO')
    eq('\nasdf\n', fn('prompt_getinput', buf))
  end)

  it('deleting prompt adds it back on insert', function()
    source_script()
    feed('asdf')
    screen:expect([[
      cmd: asdf^                |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>ddi')
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3

Review comment (suggestion):
```suggestion
    -- newline created with o/O is reflected in prompt_getinput
```

Code after:
      line 3"                  |
      cmd: ^                    |
      {3:[Prompt]                 }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('line 4<s-cr>line 5')

    feed('<esc>k0oafter prompt')
    screen:expect([[
      after                    |
      line 3"                  |
      cmd: line 4              |
      after prompt^             |
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>k0Oat prompt')
    screen:expect([[
      after                    |
      line 3"                  |
      cmd: at prompt^           |
      line 4                   |
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<cr>')
    screen:expect([[
      line 4                   |
      after prompt             |
      line 5"                  |
      cmd: ^                    |
      {3:[Prompt]                 }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])
  end)

  it('deleting prompt adds it back on insert', function()
    source_script()
    feed('asdf')
    screen:expect([[
Repository: neovim/neovim
File: test/functional/legacy/prompt_buffer_spec.lua (Lua)

Code before:
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])
  end)

  it("sets the ': mark", function()
    source_script()
    feed('asdf')
    eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
    feed('<cr>')
    eq({ 3, 1 }, api.nvim_buf_get_mark(0, ':'))
    -- Multiline prompt.
    feed('<s-cr>line1<s-cr>line2<s-cr>line3<cr>')
    eq({ 11, 1 }, api.nvim_buf_get_mark(0, ':'))

    -- ': mark is only available in prompt buffer.
    source('set buftype=')
    eq("Invalid mark name: ':'", t.pcall_err(api.nvim_buf_get_mark, 0, ':'))
  end)

  it('% prompt is not repeated with fomratopt r', function()
    source([[
      set formatoptions+=r
      set buftype=prompt
    ]])

    feed('iline1<s-cr>line2')

    screen:expect([[
      % line1                  |
      line2^                    |
      {1:~                        }|*7
      {5:-- INSERT --}             |
    ]])
  end)
end)


Review comment (suggestion):
```suggestion
  it('% prompt is not repeated with formatoptions+=r', function()
```

Code after:
    feed('asdf')
    screen:expect([[
      cmd: asdf^                |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>ddi')
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('asdf')
    screen:expect([[
      cmd: asdf^                |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>cc')
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])
  end)

  it("sets the ': mark", function()
    source_script()
    feed('asdf')
    eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
    feed('<cr>')
    eq({ 3, 1 }, api.nvim_buf_get_mark(0, ':'))
    -- Multiline prompt.
    feed('<s-cr>line1<s-cr>line2<s-cr>line3<cr>')
    eq({ 11, 1 }, api.nvim_buf_get_mark(0, ':'))
Repository: neovim/neovim
File: test/functional/legacy/prompt_buffer_spec.lua (Lua)

Code before:
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])
  end)

  it("sets the ': mark", function()
    source_script()
    feed('asdf')
    eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
    feed('<cr>')
    eq({ 3, 1 }, api.nvim_buf_get_mark(0, ':'))
    -- Multiline prompt.
    feed('<s-cr>line1<s-cr>line2<s-cr>line3<cr>')
    eq({ 11, 1 }, api.nvim_buf_get_mark(0, ':'))

    -- ': mark is only available in prompt buffer.
    source('set buftype=')
    eq("Invalid mark name: ':'", t.pcall_err(api.nvim_buf_get_mark, 0, ':'))
  end)

  it('% prompt is not repeated with fomratoption+=r', function()
    source([[
      set formatoptions+=r
      set buftype=prompt
    ]])

    feed('iline1<s-cr>line2')

    screen:expect([[
      % line1                  |
      line2^                    |
      {1:~                        }|*7
      {5:-- INSERT --}             |
    ]])
  end)
end)


Review comment (bug):
typo

can this be combined with any existing test? or if not, maybe eventually. there are zillions of edge cases that likely need to be tested, and it's probably too noisy to have a separate test for each case. something like `it('interactive editing')` could make sense to confirm how the editing should work with various options set.

Code after:
    feed('asdf')
    screen:expect([[
      cmd: asdf^                |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>ddi')
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('asdf')
    screen:expect([[
      cmd: asdf^                |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])

    feed('<esc>cc')
    screen:expect([[
      cmd: ^                    |
      {1:~                        }|*3
      {3:[Prompt] [+]             }|
      other buffer             |
      {1:~                        }|*3
      {5:-- INSERT --}             |
    ]])
  end)

  it("sets the ': mark", function()
    source_script()
    feed('asdf')
    eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
    feed('<cr>')
    eq({ 3, 1 }, api.nvim_buf_get_mark(0, ':'))
    -- Multiline prompt.
    feed('<s-cr>line1<s-cr>line2<s-cr>line3<cr>')
    eq({ 11, 1 }, api.nvim_buf_get_mark(0, ':'))
Repository: neovim/neovim
File: src/nvim/api/ui.c (C)

Code before:

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h"
# include "ui_events_remote.generated.h"  // IWYU pragma: export
#endif

// TODO(bfredl): just make UI:s owned by their channels instead
static PMap(uint64_t) connected_uis = MAP_INIT;

static char *mpack_array_dyn16(char **buf)
{
  mpack_w(buf, 0xdc);
  char *pos = *buf;
  mpack_w2(buf, 0xFFEF);
  return pos;
}

static void mpack_str_small(char **buf, const char *str, size_t len)
{
  assert(len < 0x20);
  mpack_w(buf, 0xa0 | len);
  memcpy(*buf, str, len);
  *buf += len;
}

uint64_t get_ui_number(void)
{
  return map_size(&connected_uis);
}

static void remote_ui_destroy(RemoteUI *ui)
  FUNC_ATTR_NONNULL_ALL
{
  xfree(ui->packer.startptr);
  XFREE_CLEAR(ui->term_name);
  xfree(ui);
}

/// Removes the client on the given channel from the list of UIs.
///
/// @param err  if non-NULL and there is no UI on the channel, set an error
/// @param send_error_exit  send an "error_exit" event with 0 status first
void remote_ui_disconnect(uint64_t channel_id, Error *err, bool send_error_exit)
{
  RemoteUI *ui = pmap_get(uint64_t)(&connected_uis, channel_id);
  if (!ui) {
    if (err != NULL) {
      api_set_error(err, kErrorTypeException,
                    "UI not attached to channel: %" PRId64, channel_id);
    }
    return;

Review comment (suggestion):
`ui_active` already gives this info https://github.com/neovim/neovim/blob/efd0fa55c8b1ba78692382c52a9d644fdd5488c8/src/nvim/ui.c#L186-L187

Code after:

#include "api/ui.c.generated.h"
#include "ui_events_remote.generated.h"  // IWYU pragma: export

// TODO(bfredl): just make UI:s owned by their channels instead
static PMap(uint64_t) connected_uis = MAP_INIT;

/// Gets the UI attached to the given channel, or sets an error message on `err`.
static RemoteUI *get_ui_or_err(uint64_t chan_id, Error *err)
{
  RemoteUI *ui = pmap_get(uint64_t)(&connected_uis, chan_id);
  if (ui == NULL && err != NULL) {
    api_set_error(err, kErrorTypeException, "UI not attached to channel: %" PRId64, chan_id);
  }
  return ui;
}

static char *mpack_array_dyn16(char **buf)
{
  mpack_w(buf, 0xdc);
  char *pos = *buf;
  mpack_w2(buf, 0xFFEF);
  return pos;
}

static void mpack_str_small(char **buf, const char *str, size_t len)
{
  assert(len < 0x20);
  mpack_w(buf, 0xa0 | len);
  memcpy(*buf, str, len);
  *buf += len;
}

static void remote_ui_destroy(RemoteUI *ui)
  FUNC_ATTR_NONNULL_ALL
{
  xfree(ui->packer.startptr);
  XFREE_CLEAR(ui->term_name);
  xfree(ui);
}

/// Removes the client on the given channel from the list of UIs.
///
/// @param err  if non-NULL and there is no UI on the channel, set an error
/// @param send_error_exit  send an "error_exit" event with 0 status first
void remote_ui_disconnect(uint64_t channel_id, Error *err, bool send_error_exit)
{
  RemoteUI *ui = get_ui_or_err(channel_id, err);
  if (!ui) {
    return;
  }
Repository: neovim/neovim
File: src/nvim/ex_docmd.c (C)

Code before:
      return;
    }

    // Server-side channel close.
    const char *err = NULL;
    bool rv = channel_close(chan->id, kChannelPartAll, &err);
    if (!rv && err) {
      emsg(err);  // UI disappeared already?
      return;
    }
    // XXX: Can't do this, channel_decref() is async...
    // assert(!find_channel(chan->id));

    ILOG("detach current_ui=%" PRId64, chan->id);
  }
}

/// ":connect"
///
/// Connects the current UI to a different server
///
/// ":connect <address>" connects the currently running TUI to the server at the
/// given address and detaches the connected server
/// ":connect! <address>" (with the bang (!)) connects the currently running TUI
/// to the server at the given address and stops the connected server if no
/// other UIs are attached to it
static void ex_connect(exarg_T *eap)
{
  if (eap->argc > 1) {
    emsg("connect: only one argument is allowed");
    return;
  }

  bool stop_server = false;
  if (eap->forceit) {
    stop_server = (get_ui_number() == 1);
  }

  Error err = ERROR_INIT;
  remote_ui_connect(current_ui, eap->arg, &err);
  if (ERROR_SET(&err)) {
    emsg(err.msg);
    api_clear_error(&err);
  }

  if (stop_server) {
    exiting = true;
    getout(0);
  } else {
    ex_detach(NULL);
  }

Review comment (suggestion):
```suggestion
/// ":connect <address>" detaches the current UI and connects to the given server.
/// ":connect! <address>" stops the current server if no other UIs are attached, then connects to the given server.
```

Code after:
      msg_putchar('\n');
      msg_putchar(wp == curwin ? '>' : wp == lastused_win ? '#' : ' ');
      msg_putchar(' ');
      msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' ');
      msg_putchar(' ');
      if (buf_spname(wp->w_buffer) != NULL) {
        xstrlcpy(IObuff, buf_spname(wp->w_buffer), IOSIZE);
      } else {
        home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true);
      }
      msg_outtrans(IObuff, 0, false);
      os_breakcheck();
    }
  }
}

/// ":detach"
///
/// Detaches the current UI.
///
/// ":detach!" with bang (!) detaches all UIs _except_ the current UI.
static void ex_detach(exarg_T *eap)
{
  // come on pooky let's burn this mf down
  if (eap && eap->forceit) {
    emsg("bang (!) not supported yet");
  } else {
    // 1. Send "error_exit" UI-event (notification only).
    // 2. Perform server-side UI detach.
    // 3. Close server-side channel without self-exit.

    if (!current_ui) {
      emsg("UI not attached");
      return;
    }

    Channel *chan = find_channel(current_ui);
    if (!chan) {
      emsg(e_invchan);
      return;
    }
    chan->detach = true;  // Prevent self-exit on channel-close.

    // Server-side UI detach. Doesn't close the channel.
    Error err2 = ERROR_INIT;
    remote_ui_disconnect(chan->id, &err2, true);
    if (ERROR_SET(&err2)) {
      emsg(err2.msg);  // UI disappeared already?
      api_clear_error(&err2);
      return;
    }
Repository: neovim/neovim
File: src/nvim/ex_cmds.lua (Lua)

Code before:
    command = 'cnfile',
    flags = bit.bor(RANGE, COUNT, TRLBAR, BANG),
    addr_type = 'ADDR_UNSIGNED',
    func = 'ex_cnext',
  },
  {
    command = 'cnoremap',
    flags = bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_map',
  },
  {
    command = 'cnoreabbrev',
    flags = bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_abbreviate',
  },
  {
    command = 'cnoremenu',
    flags = bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_OTHER',
    func = 'ex_menu',
  },
  {
    command = 'connect',
    flags = bit.bor(BANG, EXTRA, NOSPC, NEEDARG),
    addr_type = 'ADDR_NONE',
    func = 'ex_connect',
  },
  {
    command = 'copy',
    flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
    addr_type = 'ADDR_LINES',
    func = 'ex_copymove',
  },
  {
    command = 'colder',
    flags = bit.bor(RANGE, COUNT, TRLBAR),
    addr_type = 'ADDR_UNSIGNED',
    func = 'qf_age',
  },
  {
    command = 'colorscheme',
    flags = bit.bor(WORD1, TRLBAR, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_colorscheme',
  },
  {
    command = 'command',
    flags = bit.bor(EXTRA, BANG, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',

Review comment (suggestion):
Please check if there's a flag that ensures that "%", "#", etc. are not treated specially. They should be treated literally in the server arg. E.g. "%" should not be expanded to the current buffer name, it should be treated as a literal "%". And we need a test for this.

Code after:
    command = 'cnfile',
    flags = bit.bor(RANGE, COUNT, TRLBAR, BANG),
    addr_type = 'ADDR_UNSIGNED',
    func = 'ex_cnext',
  },
  {
    command = 'cnoremap',
    flags = bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_map',
  },
  {
    command = 'cnoreabbrev',
    flags = bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_abbreviate',
  },
  {
    command = 'cnoremenu',
    flags = bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_OTHER',
    func = 'ex_menu',
  },
  {
    command = 'connect',
    flags = bit.bor(BANG, WORD1, NOTRLCOM, NEEDARG),
    addr_type = 'ADDR_NONE',
    func = 'ex_connect',
  },
  {
    command = 'copy',
    flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
    addr_type = 'ADDR_LINES',
    func = 'ex_copymove',
  },
  {
    command = 'colder',
    flags = bit.bor(RANGE, COUNT, TRLBAR),
    addr_type = 'ADDR_UNSIGNED',
    func = 'qf_age',
  },
  {
    command = 'colorscheme',
    flags = bit.bor(WORD1, TRLBAR, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
    func = 'ex_colorscheme',
  },
  {
    command = 'command',
    flags = bit.bor(EXTRA, BANG, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK),
    addr_type = 'ADDR_NONE',
Repository: neovim/neovim
File: src/nvim/ex_docmd.c (C)

Code before:
    ILOG("detach current_ui=%" PRId64, chan->id);
  }
}

/// ":connect"
///
/// Connects the current UI to a different server
///
/// ":connect <address>" connects the currently running TUI to the server at the
/// given address and detaches the connected server
/// ":connect! <address>" (with the bang (!)) connects the currently running TUI
/// to the server at the given address and stops the connected server if no
/// other UIs are attached to it
static void ex_connect(exarg_T *eap)
{
  if (eap->argc > 1) {
    emsg("connect: only one argument is allowed");
    return;
  }

  bool stop_server = false;
  if (eap->forceit) {
    stop_server = (get_ui_number() == 1);
  }

  Error err = ERROR_INIT;
  remote_ui_connect(current_ui, eap->arg, &err);
  if (ERROR_SET(&err)) {
    emsg(err.msg);
    api_clear_error(&err);
  }

  if (stop_server) {
    exiting = true;
    getout(0);
  } else {
    ex_detach(NULL);
  }
}

/// ":mode":
/// If no argument given, get the screen size and redraw.
static void ex_mode(exarg_T *eap)
{
  if (*eap->arg == NUL) {
    must_redraw = UPD_CLEAR;
    ex_redraw(eap);
  } else {
    emsg(_(e_screenmode));
  }
}

Review comment (bug):
detach should probably happen before `remote_ui_connect` ? seems wrong to emit a "connect" event followed by "detach".

Code after:
  }
}

/// ":detach"
///
/// Detaches the current UI.
///
/// ":detach!" with bang (!) detaches all UIs _except_ the current UI.
static void ex_detach(exarg_T *eap)
{
  // come on pooky let's burn this mf down
  if (eap && eap->forceit) {
    emsg("bang (!) not supported yet");
  } else {
    // 1. Send "error_exit" UI-event (notification only).
    // 2. Perform server-side UI detach.
    // 3. Close server-side channel without self-exit.

    if (!current_ui) {
      emsg("UI not attached");
      return;
    }

    Channel *chan = find_channel(current_ui);
    if (!chan) {
      emsg(e_invchan);
      return;
    }
    chan->detach = true;  // Prevent self-exit on channel-close.

    // Server-side UI detach. Doesn't close the channel.
    Error err2 = ERROR_INIT;
    remote_ui_disconnect(chan->id, &err2, true);
    if (ERROR_SET(&err2)) {
      emsg(err2.msg);  // UI disappeared already?
      api_clear_error(&err2);
      return;
    }

    // Server-side channel close.
    const char *err = NULL;
    bool rv = channel_close(chan->id, kChannelPartAll, &err);
    if (!rv && err) {
      emsg(err);  // UI disappeared already?
      return;
    }
    // XXX: Can't do this, channel_decref() is async...
    // assert(!find_channel(chan->id));

    ILOG("detach current_ui=%" PRId64, chan->id);
  }