At startup, how to detect if my frontend is built-in TUI, or something else?

I want to use certain settings for the built-in TUI frontend, and other settings in other GUI frontends like Neovide.

GUI frontends usually (but don’t always) source ginit, so the simple solution is to set my TUI colorscheme in init.vim/init.lua and then my GUI colorscheme in ginit.vim.

However it would be nice to avoid running my TUI-specific setup code entirely when not connecting to a TUI, because I have some other settings (such as cursorline) that I like to set differently. Also, a few frontends (notably VimR) don’t use ginit.vim.

Is there a reliable way to do this? The best guess I’ve had so far is to get v:event.chan within a UIEnter autocommand and use it to inspect the output of nvim_list_chans() for the current UI channel. Here’s what I put together so far:

let g:term_color =
  \ local#utils#isin($TERMCOLOR, ['light', 'dark'])
  \ ? $TERMCOLOR
  \ : 'dark'

let g:tui_color_schemes = {'light': 'dim', 'dark': 'dim'}
let g:gui_color_scheme = 'catppuccin-frappe'

function s:ui_setup_colors() abort
  try
    let l:event_chan = v:event.chan
  catch /^Vim\%((\a\+)\)\=:E716:/
    echomsg 'init-extra.vim:s:ui_setup_colors() was called outside of a UIEnter event. Aborting.'
    return
  endtry

  try
    let l:current_chan = filter(nvim_list_chans(), {_, x -> x.id == l:event_chan})[0]
    let l:current_ui = filter(nvim_list_uis(), {_, x -> x.chan == l:event_chan})[0]
  catch /^Vim\%((\a\+)\)\=:E684:/
    echomsg printf('Channel or UI with event chan ID of %s is missing; something went wrong. Aborting.', l:event_chan)
  endtry

  echo l:current_chan
  echo l:current_ui

  let l:is_tui = empty(l:current_chan.client)
  if l:is_tui
    let &background = g:term_color
    let l:colorscheme = g:tui_color_schemes[g:term_color]
    echomsg printf('TUI detected, setting colorscheme "%s".', l:colorscheme)
    try
      call nvim_cmd(#{cmd: 'colorscheme', args: [l:colorscheme]}, {})
    catch /Vim\%((\a\+)\)\=:E185:/
      echomsg printf('Color scheme "%s" is not available.', l:colorscheme)
    endtry
  else
    let l:colorscheme = g:gui_color_scheme
    echomsg printf('GUI detected, setting colorscheme "%s".', l:colorscheme)
    try
      call nvim_cmd(#{cmd: 'colorscheme', args: [l:colorscheme]}, {})
    catch /Vim\%((\a\+)\)\=:E185:/
      echomsg printf('Color scheme "%s" is not available.', l:colorscheme)
    endtry
  endif
endfunction

augroup vimrc_init
  autocmd UIEnter * call s:ui_setup_colors()
augroup END

This works for Neovide and FVim, but not for Neovim-Qt. It seems like the l:current_chan for Neovim-Qt at the time UIEnter is triggered has an empty .client attribute, which is only filled some time after UIEnter is triggered but before (or immediately after) the UI appears, so the TUI settings get applied when Neovim-Qt is starting up.

Is this a bug or behavioral deficiency in Neovim-Qt specifically? Or am I just doing it wrong? I can work around it by calling this function in ginit.vim, which Neovim-Qt seems to run after UIEnter (whereas Neovide and FVim seem to run ginit.vim it before UIEnter).

if has('gui_running')
" This is in GUI
endif

See `:help has()`
1 Like