b0o / incline.nvim

🎈 Floating statuslines for Neovim, winbar alternative
MIT License
788 stars 14 forks source link

Dynamically add separators #68

Closed toupeira closed 5 months ago

toupeira commented 5 months ago

I'd like to render several sections which are all optional (diagnostics, Aerial breadcrumbs, filename only if more than one window is open), and add separator characters between them.

It doesn't seem like there's a way to do this with the plugin, would you consider adding it as an option? It could just be a single string that gets inserted between non-empty sections.

I could do this formatting myself inside render() and return the final string, but then I lose the ability to also set highlight groups. Unless I'm missing something?

b0o commented 5 months ago

This should be possible with the existing render function. If you could share your current configuration, I can try to assist with this.

toupeira commented 5 months ago

Thanks! Here's my current setup: https://github.com/toupeira/dotfiles/blob/main/vim/plugins/incline.lua

And a screenshot for reference:

image

I settled on filename + symbol breadcrumbs for now, and I'll probably keep the diagnostics in the statusline to avoid making the incline too distracting.

The filename is only shown if there's more than one window, and the symbols are only shown for the current buffer. So with only 2 sections it's not really a problem, I just conditionally include the separator before the symbols by repeating the check for number of windows:

return (props.windows > 1 and separator or '') .. symbols

It only becomes tricky if I wanted to add another section, and have to check in each section whether other sections are shown.

BTW I'm also abusing Aerial's Lualine component to generate the content for the symbols, was surprised that this worked! :grinning:

b0o commented 5 months ago

Here's what I would do. I changed some of the following code:

local function get_filename(props)
  if props.windows <= 1 then
    return nil
  end

  local path = vim.api.nvim_buf_get_name(props.buf)
  local filename = vim.fn.fnamemodify(path, ':t')
  if filename == '' then
    filename = '[No Name]'
  end

  return {
    ' ' .. filename,
    gui = 'bold',
    guifg = props.focused and 'white' or comment,
  }
end

local aerial
local function get_symbols(props)
  if not props.focused then
    return nil
  end

  if not aerial then
    aerial = require 'lualine.components.aerial' {
      self = { section = 'x' },
      icons_enabled = true,
      sep = separator,
    }
  end

  local symbols = vim.api.nvim_eval_statusline(aerial:get_status(), { winid = props.win }).str
  if symbols == '' then
    return nil
  end
  return symbols
end

opts.render = function(props)
  props.windows = get_window_count()

  local sections = {
    get_filename(props),
    get_symbols(props),
  }

  local result = {}
  for _, section in pairs(sections) do
    if section and section ~= '' then
      if #result > 0 then
        table.insert(result, separator)
      end
      table.insert(result, section)
    end
  end
  return result
end

Now, you can add additional sections to the sections table in opts.render(). Then, the loop that builds the result table will insert separators between sections, skipping empty ones. With this approach, you can still set highlight groups.


P.s seeing your use of vim.api.nvim_eval_statusline gave me an idea: I created a helper function in incline.helpers that will evaluate a statusline string and convert it to an incline render result, including support for highlights (see 49cd56c1fd5166bae7f2af63c7eeb3489fe484af). Here's a version of your get_symbols() function that uses it:

local helpers = require 'incline.helpers'
local aerial

local function get_symbols(props)
  if not props.focused then
    return nil
  end

  if not aerial then
    aerial = require 'lualine.components.aerial' {
      self = { section = 'x' },
      icons_enabled = true,
      sep = separator,
      sep_highlight = 'InclineNormal',
    }
  end

  local aerial_statusline = vim.api.nvim_win_call(props.win, function()
    return aerial:get_status { winid = props.win }
  end)

  return helpers.eval_statusline(aerial_statusline, { winid = props.win })
end

It looks like this, now supporting highlights:

2024-04-11_17-17-48_region

You could also set a highlight group common to all of the separators if you want:

--- unchanged code omitted ---

local sep = { ' î‚» ', group = 'NonText' }

local helpers = require 'incline.helpers'

local aerial

local function get_symbols(props)
  if not props.focused then
    return nil
  end

  if not aerial then
    aerial = require 'lualine.components.aerial' {
      self = { section = 'x' },
      icons_enabled = true,
      sep = sep[1],
      sep_highlight = sep.group,
    }
  end

  local aerial_statusline = vim.api.nvim_win_call(props.win, function()
    return aerial:get_status { winid = props.win }
  end)

  return helpers.eval_statusline(aerial_statusline, { winid = props.win })
end

require('incline').setup {
  render = function(props)
    props.windows = get_window_count()

    local sections = {
      get_filename(props),
      get_symbols(props),
    }

    local result = {}
    for _, section in pairs(sections) do
      if section and section ~= '' and not (type(section) == 'table' and #section == 0) then
        if #result > 0 then
          table.insert(result, sep)
        end
        table.insert(result, section)
      end
    end
    return result
  end,
  window = {
    margin = { horizontal = 0, vertical = 0 },
  },
}

Which looks like this:

2024-04-11_17-25-02_region

Putting it all together:

return {
  'b0o/incline.nvim',
  event = 'VeryLazy',

  opts = {
    window = {
      margin = { horizontal = 0, vertical = 0 },
    },
  },

  config = function(_, opts)
    local helpers = require 'incline.helpers'

    local sep = { ' î‚» ', group = 'NonText' }
    local comment = require('util').get_color 'Comment'

    local function get_window_count()
      local windows = vim.api.nvim_tabpage_list_wins(0)
      return #vim.tbl_filter(function(win)
        return vim.api.nvim_win_get_config(win).relative == ''
      end, windows)
    end

    local function get_filename(props)
      local path = vim.api.nvim_buf_get_name(props.buf)
      local filename = vim.fn.fnamemodify(path, ':t')
      if filename == '' then
        filename = '[No Name]'
      end

      return {
        ' ' .. filename,
        gui = 'bold',
        guifg = props.focused and 'white' or comment,
      }
    end

    local aerial

    local function get_symbols(props)
      if not props.focused then
        return nil
      end

      if not aerial then
        aerial = require 'lualine.components.aerial' {
          self = { section = 'x' },
          icons_enabled = true,
          sep = sep[1],
          sep_highlight = sep.group,
        }
      end

      local aerial_statusline = vim.api.nvim_win_call(props.win, function()
        return aerial:get_status { winid = props.win }
      end)

      return helpers.eval_statusline(aerial_statusline, { winid = props.win })
    end

    opts.render = function(props)
      local window_count = get_window_count()

      local sections = {
        window_count > 1 and get_filename(props) or nil,
        get_symbols(props),
      }

      local result = {}
      for _, section in pairs(sections) do
        if section and section ~= '' and not (type(section) == 'table' and #section == 0) then
          if #result > 0 then
            table.insert(result, sep)
          end
          table.insert(result, section)
        end
      end
      return result
    end

    require('incline').setup(opts)
  end,
}

Hopefully this gives you some ideas around what's possible.

toupeira commented 5 months ago

@b0o thank you very much, that all works great! :bow: Adapted in https://github.com/toupeira/dotfiles/blob/39464e381811f7c4386ea409f218167da71c5a53/vim/plugins/incline.lua.

Using nvim_win_call to run Aerial also fixes the problem I had where unfocused windows would all show the symbol for the current window's cursor, rather than their own. This was the reason I was only showing symbols in the current window :grinning: But now I've tweaked it to always show symbols, and hide the filename instead if there isn't more than one listed buffer.

I'll go ahead and close this issue!