famiu / feline.nvim

A minimal, stylish and customizable statusline for Neovim written in Lua
GNU General Public License v3.0
1.04k stars 55 forks source link

[Feature] Custom statusline for specific buffer-/filetypes #141

Closed jonatan-branting closed 2 years ago

jonatan-branting commented 3 years ago

Right now, it is only possible to have two different statuslines defined, inactive and active. If you want a very minimal statusline for project drawers, file finders, etc. you would have to give up relevant information for normal inactive windows.

It should be possible to have inactive and active statuslines to be quite similar, while still allowing for a very minimal statusline for project drawers, file finders, etc.

A solution to this would be to allow the user to force custom statuslines (In the same vein as inactive and active) on specific buffer-/filetypes. I know that it is possible to force the inactive statusline to always be on for certain buffer-/filetypes, but this is not flexible enough.

An example configuration could look something like this:

properties.custom_statuslines_by_buftype = {
  'startify' = startify_statusline,
  'fern' = minimal_statusline,
  'Telescope' = file_finder_statusline,
}

Where these statuslines are defined just as active and inactive are defined.

rebelot commented 3 years ago

107

famiu commented 2 years ago

How do you suggest for this to be implemented? Like, how would you configure Feline to have different component tables? The syntax you showed is invalid in Lua and there is no properties value anymore in Feline. Also, what if you want different statuslines for the active and inactive statuslines of a specific buffer type, buffer name or filetype?

jonatan-branting commented 2 years ago

How do you suggest for this to be implemented? Like, how would you configure Feline to have different component tables? The syntax you showed is invalid in Lua and there is no properties value anymore in Feline. Also, what if you want different statuslines for the active and inactive statuslines of a specific buffer type, buffer name or filetype?

I was thinking that this would be implemented similarly to force_inactive, meaning that if you have a custom statusline specified for a given filetype/buftype, this statusline would always be active, effectively ignoring the active/inactive flag.

-- Slightly more syntactically correct example
local statusline_overrides = {
  { filetype = "^NvimTree$", statusline = minimal_statusline },
  { filetype = "^fern$",  statusline = minimal_statusline },
  { filetype = "^startify$", statusline = startify_statusline },
  { filetype = "^Telescope$", statusline = file_finder_statusline },
}

require('feline').setup({
  statusline_overrides = statusline_overrides
})

Although a more powerful, albeit a bit more complex option would be to simply allow the component table itself to be overridden, with something like this:

local component_overrides = {
  { filetype = "^NvimTree$",  components =  {
    active = minimal_statusline(true), inactive = minimal_statusline(false)}
  },
  { filetype = "^Telescope$",  components =  {
    active =  file_finder_statusline(true), inactive = file_finder_statusline(false)}
  },
}

require('feline').setup({
  component_overrides = component_overrides
})

Allowing more than one filetype or buftype to be overridden at once could be implemented as:

local component_overrides = {
  { filetypes = { "^NvimTree$", "fern" }, buftypes = { "terminal" },  components =  {
    active = minimal_statusline(true), inactive = minimal_statusline(false)}
  },
}

require('feline').setup({
  component_overrides = component_overrides
})

There are probably better ways of achieving this, but I reckon this gets the point across!

rebelot commented 2 years ago

My (unrequested) 2 cents, I'd remove force_inactive in favor of this more versatile alternative (the same behavior could be reproduced setting active and inactive to the same component lists).


-- completely random opts
local special_components1  = {
  active = { {{provider='mode'}, {provider='filename'}}, {}, {provider='percent'} },
  inactive = {{provider='filename'}},
}

local special_components2 = {
  active = {{provider='momasaysImspecial!'}, {provider='filetype'}}
  inactive = {{provider='momasaysImspecial!'}}
}

local custom_components = {
  {  
    {filetype = 'qf', buftype= 'terminal'},
    special_components1
  },
  {  
    {filetype = 'Trouble', filename='.*%.py'},
    special_components2
  }
}
require'feline'.setup{ custom_components = custom_components }
famiu commented 2 years ago

My (unrequested) 2 cents, I'd remove force_inactive in favor of this more versatile alternative (the same behavior could be reproduced setting active and inactive to the same component lists).

While that'd certainly make sense to do since it's unreasonable to keep 2 ways of achieving the same thing, it'd also make configuration a lot more complex for people who just want to have the inactive statusline for certain buffers, wouldn't it?

rebelot commented 2 years ago

True, but I don't think that should be discouraging, as long as the readme offers an example config that's just one copy-paste away. after all lazy users or those who don't like lua still use airline. I mean, isn't this plugin made on purpose for those who wish to get their hands a little dirty to have their dreams statusline?

famiu commented 2 years ago

True, but I don't think that should be discouraging, as long as the readme offers an example config that's just one copy-paste away. after all lazy users or those who don't like lua still use airline.

I suppose, but I'll wait to think and see if I can think of a better alternative

I mean, isn't this plugin made on purpose for those who wish to get their hands a little dirty to have their dreams statusline?

Not quite, it's supposed to have reasonable defaults while also making it easy enough to configure things if you do wish to get your hands dirty

rebelot commented 2 years ago

Are you concerned about the impact on user-friendliness or do you foresee performance/implementation issues?

famiu commented 2 years ago

Are you concerned about the impact on user-friendliness or do you foresee performance/implementation issues?

Mostly the former, especially since what you're asking for is already possible using enabled, and it's such a niche usecase that I don't really see the merit in adding entirely new and complicated syntax for it

UnderCooled commented 2 years ago

I don't know whether I can describe my issue correctly.

In my old lightline config for Vim: Cursor in normal file buffer: image Cursor in Help buffer: image Help is a special buffer, it's using the vi-mode section as it's title.

In my feline config for NeoVim: Cursor in normal file buffer: image Cursor in Help buffer: image You can see that because I'm using "force inactive" setting, my special buffer title goes into every statuslines, because vim.g.actual_curbuf is this special buffer, it's active, but it's in "force inactive", so other inactive windows' statusline is using inactive section, so they all showing this special section for the title.

My helper functions and force inactive settings:

(def- force-inactive {:filetypes ["^packer$"
                                  "^NvimTree$"
                                  "^Trouble$"]
                      :buftypes ["^help$"
                                 "^quickfix$"
                                 "^terminal$"]
                      :bufnames []})

(defn- match-fi [what where]
  (core.some #(string.find what $1) where))

(defn- get-win-buf []
  (tonumber vim.g.actual_curbuf))

(defn- in-fi? []
  (local buf (get-win-buf))
  (or (match-fi (nvim.buf_get_option buf :filetype) force-inactive.filetypes)
      (match-fi (nvim.buf_get_option buf :buftype) force-inactive.buftypes)
      (match-fi (nvim.buf_get_name buf) force-inactive.bufnames)))

(defn- inactive-title []
  (if (in-fi?)
    (do
      (local buf (get-win-buf))
      (local filetype (nvim.buf_get_option buf :filetype))
      (local buftype (nvim.buf_get_option buf :buftype))
      (local bufname (nvim.buf_get_name buf))
      (local name (if (not= filetype "") filetype
                      (not= buftype "") buftype
                      bufname))
      (string.format " %s " (string.upper name)))
    ""))

My statusline section for this inactive title:

2 {:provider inactive-title
    :enabled in-fi?
    :hl {:bg :yellow_br :fg :black}
    :right_sep "slant_right_2"}

You can see that to make specific buffer statusline with this force inactive "idea" is not really that straitforward. You need to check buftype/filetype/bufname, and if I want to make it works like lightline, I might have to modify my statusline active section, that's basically hook this in-fi? function in all providers.

My feline config:

(module config.plugin.feline
  {autoload {nvim aniseed.nvim
             core aniseed.core
             cpt catppuccino.api.colors
             devi nvim-web-devicons
             : feline
             lsp feline.providers.lsp
             fe-defaults feline.defaults
             fe-file feline.providers.file
             vi-mode feline.providers.vi_mode}})
(def- colors (match (cpt.get_colors)
                ({:status false} msg) (print msg)
                ({:status true} cs) cs))

(def- vi_mode_colors {:NORMAL :blue
                      :OP :blue_br
                      :INSERT :green
                      :VISUAL :magenta
                      :BLOCK :magenta_br
                      :REPLACE :red_br
                      "V-REPLACE" :red
                      :LINES :pink
                      :SELECT :cyan
                      :TERM :yellow
                      :COMMAND :orange})

(def- force-inactive {:filetypes ["^packer$"
                                  "^NvimTree$"
                                  "^Trouble$"]
                      :buftypes ["^help$"
                                 "^quickfix$"
                                 "^terminal$"]
                      :bufnames []})

(defn- match-fi [what where]
  (core.some #(string.find what $1) where))

(defn- get-win-buf []
  (tonumber vim.g.actual_curbuf))

(defn- in-fi? []
  (local buf (get-win-buf))
  (or (match-fi (nvim.buf_get_option buf :filetype) force-inactive.filetypes)
      (match-fi (nvim.buf_get_option buf :buftype) force-inactive.buftypes)
      (match-fi (nvim.buf_get_name buf) force-inactive.bufnames)))

(defn- inactive-title []
  (if (in-fi?)
    (do
      (local buf (get-win-buf))
      (local filetype (nvim.buf_get_option buf :filetype))
      (local buftype (nvim.buf_get_option buf :buftype))
      (local bufname (nvim.buf_get_name buf))
      (local name (if (not= filetype "") filetype
                      (not= buftype "") buftype
                      bufname))
      (string.format " %s " (string.upper name)))
    ""))

(defn- get-file-type-color []
  (local bufname (nvim.buf_get_name (get-win-buf)))
  (let [(icon name) (devi.get_icon (nvim.fn.fnamemodify bufname ":t") (nvim.fn.fnamemodify bufname ":e"))]
    (if icon (nvim.fn.synIDattr (nvim.fn.hlID name) "fg") "white")))

(defn- get-file-type-bg-hl []
  {:fg :black :bg (get-file-type-color)})

(defn- get-sep-str [s]
  (core.get fe-defaults.separators s s))

(defn- get-file-type-sep [sep]
  (local str (get-sep-str sep))
  {: str :hl {:fg (get-file-type-color)}})

(defn- get-file-type-sep-inv [sep]
  (local str (get-sep-str sep))
  {: str :hl {:bg (get-file-type-color)}})

(defn- diag? [diag-type]
  (lsp.diagnostics_exist diag-type (get-win-buf)))

(defn- wider-than [col]
  (> (nvim.win_get_width (tonumber vim.g.actual_curwin)) col))

(def- components {
                  :active [{1 {:provider "scroll_bar"
                               :enabled #(wider-than 30)
                               :hl {:fg :blue_br}
                               :right_sep {:str "vertical_bar_thin" :hl {:fg :blue}}}
                            2 {:provider #(string.format " %s " (vi-mode.get_vim_mode))
                               :hl #(values {:bg (vi-mode.get_mode_color) :fg "black"})
                               :right_sep :slant_right}
                            3 {:provider {:name "file_info" :opts {:colored_icon false
                                                                   :file_readonly_icon " "
                                                                   :type "relative-short"}}
                               :left_sep [#(get-file-type-sep :slant_left_2) #(get-file-type-sep-inv " ")]
                               :hl #(core.merge (get-file-type-bg-hl) {:style :bold})
                               :right_sep :slant_right_2}
                            4 {:provider "position"
                               :left_sep " "
                               :right_sep [" " {:str " " :hl {:fg :white_br}} {:str "slant_right_thin" :hl {:fg :gray}}]}
                            5 {:provider "git_branch"
                               :enabled #(wider-than 80)
                               :left_sep " "
                               :hl {:fg :yellow :style :bold}}
                            6 {:provider "git_diff_added"
                               :enabled #(wider-than 80)
                               :hl {:fg :green}}
                            7 {:provider "git_diff_changed"
                               :enabled #(wider-than 80)
                               :hl {:fg :orange}}
                            8 {:provider "git_diff_removed"
                               :enabled #(wider-than 80)
                               :hl {:fg :red}}}
                           [{:provider "vi_mode"
                             :enabled #(wider-than 60)
                             :hl #(values {:name (vi-mode.get_mode_highlight_name)
                                           :fg (vi-mode.get_mode_color)})}]
                           {1 {:provider "lsp_client_names"
                               :right_sep {:str "slant_right_2_thin" :hl {:fg :gray}}
                               :hl {:fg :pink}}
                            2 {:provider "diagnostic_errors"
                               :enabled #(diag? "Error")
                               :hl {:fg :red}}
                            3 {:provider "diagnostic_warnings"
                               :enabled #(diag? "Warning")
                               :hl {:fg :yellow}}
                            4 {:provider "diagnostic_hints"
                               :enabled #(diag? "Hint")
                               :hl {:fg :cyan}}
                            5 {:provider "diagnostic_info"
                               :enabled #(diag? "Information")
                               :hl {:fg :blue}}
                            6 {:provider #(..
                                            (if (= "utf-8" nvim.bo.fileencoding) ""
                                              (string.upper nvim.bo.fileencoding))
                                            " "
                                            (if (= "unix" nvim.bo.fileformat) ""
                                              (string.format "(%s)" (string.upper nvim.bo.fileformat))))
                               :hl {:fg :red_br}}
                            7 {:provider "file_type"
                               :enabled #(wider-than 80)
                               :hl #(get-file-type-bg-hl)
                               :left_sep [" " #(get-file-type-sep :slant_left) #(get-file-type-sep-inv " ")]
                               :right_sep [#(get-file-type-sep-inv " ") #(get-file-type-sep :slant_right_2)]}
                            8 {:provider "line_percentage"
                               :hl {:fg :black :bg :blue :style :bold}
                               :left_sep ["slant_left" {:str " " :hl {:bg :blue}}]
                               :right_sep {:str " " :hl {:bg :blue}}}}]
                  :inactive [{1 {:provider "scroll_bar"
                                 :enabled #(wider-than 30)
                                 :hl {:fg :gray}
                                 :right_sep {:str "vertical_bar_thin" :hl {:fg :gray}}}
                              2 {:provider inactive-title
                                 :enabled in-fi?
                                 :hl {:bg :yellow_br :fg :black}
                                 :right_sep "slant_right_2"}
                              3 {:provider #(if (in-fi?)
                                              (fe-file.file_info {:icon false} {:file_readonly_icon " "})
                                              (fe-file.file_info $1 {:type "unique-short" :file_readonly_icon " "}))
                                 :enabled #(wider-than 30)
                                 :left_sep " "}}
                             {1 {:provider #(if (in-fi?)
                                              ""
                                              (fe-file.file_type))
                                 :enabled #(wider-than 30)
                                 :hl {:fg :black_br :bg :gray}
                                 :left_sep [" " "slant_left" {:str " " :hl {:fg :black_br :bg :gray}}]
                                 :right_sep [{:str " " :hl {:fg :black_br :bg :gray}} "slant_right_2"]}
                              2 {:provider "line_percentage"
                                 :hl {:fg :black :bg :gray :style :bold}
                                 :left_sep ["slant_left" {:str " " :hl {:bg :gray}}]
                                 :right_sep {:str " " :hl {:bg :gray}}}}]})

(feline.setup {: colors
               : components
               : vi_mode_colors
               : force-inactive})
UnderCooled commented 2 years ago

I disabled default force_inactive, and rewrite my active section, now I got this: Cursor in normal file buffer: image Cursor in Help buffer: image But the inactive section still wrong: Cursor in normal file buffer: image Those inactive filenames without file icon, I want it to be Help buffer without file icon. The R:\abc.txt should show filetype TEXT on the right side of statusline, but it won't work. Cursor in Help buffer: image Those inactive filenames and with file icon as I intended.

My feline config:

(module config.plugin.feline
  {autoload {nvim aniseed.nvim
             core aniseed.core
             cpt catppuccino.api.colors
             devi nvim-web-devicons
             : feline
             lsp feline.providers.lsp
             fe-defaults feline.defaults
             fe-file feline.providers.file
             vi-mode feline.providers.vi_mode}})
(def- colors (match (cpt.get_colors)
                ({:status false} msg) (print msg)
                ({:status true} cs) cs))

(def- vi_mode_colors {:NORMAL :blue
                      :OP :blue_br
                      :INSERT :green
                      :VISUAL :magenta
                      :BLOCK :magenta_br
                      :REPLACE :red_br
                      "V-REPLACE" :red
                      :LINES :pink
                      :SELECT :cyan
                      :TERM :yellow
                      :COMMAND :orange})

(def- special-buffers {:filetypes ["^packer$"
                                   "^NvimTree$"
                                   "^Trouble$"]
                       :buftypes ["^help$"
                                  "^quickfix$"
                                  "^terminal$"]
                       :bufnames []})

(defn- match-special [what where]
  (core.some #(string.find what $1) where))

(defn- get-win-buf []
  (tonumber vim.g.actual_curbuf))

(defn- is-special-buffer? []
  (local buf (get-win-buf))
  (or (match-special (nvim.buf_get_option buf :filetype) special-buffers.filetypes)
      (match-special (nvim.buf_get_option buf :buftype) special-buffers.buftypes)
      (match-special (nvim.buf_get_name buf) special-buffers.bufnames)))

(defn- special-title []
  (local buf (get-win-buf))
  (local filetype (nvim.buf_get_option buf :filetype))
  (local buftype (nvim.buf_get_option buf :buftype))
  (local bufname (nvim.buf_get_name buf))
  (local name (if (not= filetype "") filetype
                  (not= buftype "") buftype
                  bufname))
  (string.format " %s " (string.upper name)))

(defn- get-file-type-color []
  (local bufname (nvim.buf_get_name (get-win-buf)))
  (let [(icon name) (devi.get_icon (nvim.fn.fnamemodify bufname ":t") (nvim.fn.fnamemodify bufname ":e"))]
    (if icon (nvim.fn.synIDattr (nvim.fn.hlID name) "fg") "white")))

(defn- get-file-type-bg-hl []
  {:fg :black :bg (get-file-type-color)})

(defn- get-sep-str [s]
  (core.get fe-defaults.separators s s))

(defn- get-file-type-sep [sep]
  (local str (get-sep-str sep))
  {: str :hl {:fg (get-file-type-color)}})

(defn- get-file-type-sep-inv [sep]
  (local str (get-sep-str sep))
  {: str :hl {:bg (get-file-type-color)}})

(defn- diag? [diag-type]
  (lsp.diagnostics_exist diag-type (get-win-buf)))

(defn- wider-than [col]
  (> (nvim.win_get_width (tonumber vim.g.actual_curwin)) col))

(def- components {
                  :active [{1 {:provider "scroll_bar"
                               :enabled #(wider-than 30)
                               :hl {:fg :blue_br}
                               :right_sep {:str "vertical_bar_thin" :hl {:fg :blue}}}
                            2 {:provider #(string.format " %s " (vi-mode.get_vim_mode))
                               :enabled #(not (is-special-buffer?))
                               :hl #(values {:bg (vi-mode.get_mode_color) :fg "black"})
                               :right_sep :slant_right}
                            3 {:provider special-title
                               :enabled is-special-buffer?
                               :hl {:bg :yellow_br :fg :black}
                               :right_sep "slant_right_2"}
                            4 {:provider #(if (is-special-buffer?)
                                            (fe-file.file_info {:icon ""} {:file_readonly_icon " "})
                                            (fe-file.file_info $1 {:colored_icon false
                                                                   :file_readonly_icon " "
                                                                   :type "relative-short"}))
                               :left_sep [#(if (not (is-special-buffer?))
                                             (get-file-type-sep :slant_left_2)
                                             (get-file-type-sep :slant_left))
                                          #(get-file-type-sep-inv " ")]
                               :hl #(core.merge (get-file-type-bg-hl) {:style :bold})
                               :right_sep :slant_right_2}
                            5 {:provider "position"
                               :left_sep " "
                               :right_sep [" " {:str " " :hl {:fg :white_br}} {:str "slant_right_thin" :hl {:fg :gray}}]}
                            6 {:provider "git_branch"
                               :enabled #(wider-than 80)
                               :left_sep " "
                               :hl {:fg :yellow :style :bold}}
                            7 {:provider "git_diff_added"
                               :enabled #(wider-than 80)
                               :hl {:fg :green}}
                            8 {:provider "git_diff_changed"
                               :enabled #(wider-than 80)
                               :hl {:fg :orange}}
                            9 {:provider "git_diff_removed"
                               :enabled #(wider-than 80)
                               :hl {:fg :red}}}
                           [{:provider "vi_mode"
                             :enabled #(wider-than 60)
                             :hl #(values {:name (vi-mode.get_mode_highlight_name)
                                           :fg (vi-mode.get_mode_color)})}]
                           {1 {:provider "lsp_client_names"
                               :right_sep {:str "slant_right_2_thin" :hl {:fg :gray}}
                               :hl {:fg :pink}}
                            2 {:provider "diagnostic_errors"
                               :enabled #(diag? "Error")
                               :hl {:fg :red}}
                            3 {:provider "diagnostic_warnings"
                               :enabled #(diag? "Warning")
                               :hl {:fg :yellow}}
                            4 {:provider "diagnostic_hints"
                               :enabled #(diag? "Hint")
                               :hl {:fg :cyan}}
                            5 {:provider "diagnostic_info"
                               :enabled #(diag? "Information")
                               :hl {:fg :blue}}
                            6 {:provider #(..
                                            (if (= "utf-8" nvim.bo.fileencoding) ""
                                              (string.upper nvim.bo.fileencoding))
                                            " "
                                            (if (= "unix" nvim.bo.fileformat) ""
                                              (string.format "(%s) " (string.upper nvim.bo.fileformat))))
                               :hl {:fg :red_br}}
                            7 {:provider "file_type"
                               :enabled #(and (wider-than 80) (not (is-special-buffer?)))
                               :hl #(get-file-type-bg-hl)
                               :left_sep [" " #(get-file-type-sep :slant_left) #(get-file-type-sep-inv " ")]
                               :right_sep [#(get-file-type-sep-inv " ") #(get-file-type-sep :slant_right_2)]}
                            8 {:provider "line_percentage"
                               :hl {:fg :black :bg :blue :style :bold}
                               :left_sep ["slant_left" {:str " " :hl {:bg :blue}}]
                               :right_sep {:str " " :hl {:bg :blue}}}}]
                  :inactive [{1 {:provider "scroll_bar"
                                 :enabled #(wider-than 30)
                                 :hl {:fg :gray}
                                 :right_sep {:str "vertical_bar_thin" :hl {:fg :gray}}}
                              2 {:provider #(if (not (is-special-buffer?))
                                              (fe-file.file_info {:icon ""} {:type "unique-short" :file_readonly_icon " "})
                                              (fe-file.file_info $1 {:file_readonly_icon " "}))
                                 :enabled #(wider-than 30)
                                 :left_sep " "}}
                             {1 {:provider #(if (not (is-special-buffer?))
                                              ""
                                              (fe-file.file_type))
                                 :enabled #(wider-than 30)
                                 :hl {:fg :black_br :bg :gray}
                                 :left_sep [" " "slant_left" {:str " " :hl {:fg :black_br :bg :gray}}]
                                 :right_sep [{:str " " :hl {:fg :black_br :bg :gray}} "slant_right_2"]}
                              2 {:provider "line_percentage"
                                 :hl {:fg :black :bg :gray :style :bold}
                                 :left_sep ["slant_left" {:str " " :hl {:bg :gray}}]
                                 :right_sep {:str " " :hl {:bg :gray}}}}]})

(feline.setup {: colors
               : components
               : vi_mode_colors
               :force_inactive {}})

You can see that now is-special-buffer? checks in inactive section have to be reverted, and this whole problem still there, just move from active section to inactive section.

rebelot commented 2 years ago

I think the burden of achieving this with enabled definitely outweighs the small complexity increase of rewriting the force inactive as custom components.

local statusline = {
  active = {<components-table>},
  inactive = {<components-table>}
}
local force_inactive = {<inactive-table>}
require'feline'.setup{ force_inactive = force_inactive, components = statusline }

becomes:

local statusline = {
  active = {<components-table>},
  inactive = {<components-table>}
}
local custom_components = {
  {
    {<inactive-table>}, -- can be the same force_inactive table
    {active = statusline.inactive, inactive = statusline.inactive}
  }  -- the overhead is to re-utilize the inactive components
}
require'feline'.setup{ custom_components = custom_components, components = statusline }
famiu commented 2 years ago

@UnderCooled It seems your issue has nothing to do with this issue, please open a new issue and describe your problem there

UnderCooled commented 2 years ago

I think it's the same issue, if I want to make special buffers' statusline acting like lightline, using "force_inactive" or "enabled" will cause problem. These special buffers should be special, not just active or inactive. If other users don't need this, they can still use "force_inactive" default setting.

rebelot commented 2 years ago

If anyone still interested, heirline does that ;)

famiu commented 2 years ago

This has just been added in the develop branch and will be included in the next release of Feline