anuvyklack/pretty-fold.nvim

github github
editing-support
star 194
stars
alert-circle 7
open issues
users 3
subscribers
git-branch 7
forks
CREATED

2021-12-21

UPDATED

22 days ago


Pretty Fold

:warning: WARNING: Neovim v0.7 or higher is required

There is a 0.6 branch which is lack of some features.

Pretty Fold is a lua plugin for Neovim which has two separate features:

  • Framework for easy foldtext customization. Filetype specific and foldmethod specific configuration is supported.
  • Folded region preview (like in QtCreator).

https://user-images.githubusercontent.com/13056013/148261501-56677c8f-24a7-4c45-b008-8c1863bf06e8.mp4

Installation and quickstart

Installation and setup example with packer:

use{ 'anuvyklack/pretty-fold.nvim',
   requires = 'anuvyklack/nvim-keymap-amend' -- only for preview
   config = function()
      require('pretty-fold').setup()
      require('pretty-fold.preview').setup()
   end
}

Foldtext configuration

Pretty-fold.nvim comes with the following defaults (the description of each option is below):

config = {
   sections = {
      left = {
         'content',
      },
      right = {
         ' ', 'number_of_folded_lines', ': ', 'percentage', ' ',
         function(config) return config.fill_char:rep(3) end
      }
   },
   fill_char = '•',

   remove_fold_markers = true,

   -- Keep the indentation of the content of the fold string.
   keep_indentation = true,

   -- Possible values:
   -- "delete" : Delete all comment signs from the fold string.
   -- "spaces" : Replace all comment signs with equal number of spaces.
   -- false    : Do nothing with comment signs.
   process_comment_signs = 'spaces',

   -- Comment signs additional to the value of `&commentstring` option.
   comment_signs = {},

   -- List of patterns that will be removed from content foldtext section.
   stop_words = {
      '@brief%s*', -- (for C++) Remove '@brief' and all spaces after.
   },

   add_close_pattern = true, -- true, 'last_line' or false

   matchup_patterns = {
      {  '{', '}' },
      { '%(', ')' }, -- % to escape lua pattern char
      { '%[', ']' }, -- % to escape lua pattern char
   },

   ft_ignore = { 'neorg' },
}

sections

The main part. Contains two tables: config.sections.left and config.sections.right which content will be left and right aligned respectively. Each of them can contain names of the components and functions that returns string.

Built-in components

The strings from the table below will be expanded according to the table.

Item Expansion
'content' The content of the first non-blank line of the folded region, somehow modified according to other options.
'number_of_folded_lines' The number of folded lines.
'percentage' The percentage of the folded lines out of the whole buffer.

Custom functions

All functions accept config table as an argument, so if you would like to pass any arguments into your custom function, place them into the config table which you pass to setup function and then you can access them inside your function, like this:

require('pretty-fold').setup {
   custom_function_arg = 'Hello from inside custom function!',
   sections = {
      left = {
         function(config)
            return config.custom_function_arg
         end
      },
   }
}

image

fill_char

default: '•'

Character used to fill the space between the left and right sections.

remove_fold_markers

default: true

Remove foldmarkers from the content component.

keep_indentation

default: true

Keep the indentation of the content of the fold string.

process_comment_signs

What to do with comment signs: default: spaces

Option Description
'delete' delete all comment signs from the foldstring
'spaces' replace all comment signs with equal number of spaces
false do nothing with comment signs

comment_signs

default: {}

Table with comment signs additional to the value of &commentstring option. Add additional comment signs only when you really need them. Otherwise, they give computational overhead without any benefits.

Example for Lua. Default &commentstring value for Lua is: '--'.

comment_signs = {
    { '--[[', '--]]' }, -- multiline comment
}

Example for C++. Default &commentstring value for C++ is: { '/*', '*/' }

comment_signs = { '//' }

stop_words

default: '@brief%s*' (for C++) Remove '@brief' and all spaces after.

Lua patterns that will be removed from the content section.

add_close_pattern

default: true

If this option is set to true for all opening patterns that will be found in the first non-blank line of the folded region, all corresponding closing elements will be added after ellipsis. (The synthetical string with matching closing elements will be constructed).

If it is set to last_line, the last line content (without comments) will be added after the ellipsis . This behavior was the first algorithm I implemented, but turns out it is not always relevant. For some languages (at least for all lisps) this does not work. Since it is already written and if someone like this behavior, I keep this option to choose.

matchup_patterns

The list with matching elements. Each item is a list itself with two items: opening lua pattern and close string which will be added if oppening pattern is found.

Examples for lua (Lua patterns are explained with railroad diagrams):

matchup_patterns = {
   -- ╟─ Start of line ──╭───────╮── "do" ── End of line ─╢
   --                    ╰─ WSP ─╯
   { '^%s*do$', 'end' }, -- `do ... end` blocks

   -- ╟─ Start of line ──╭───────╮── "if" ─╢
   --                    ╰─ WSP ─╯
   { '^%s*if', 'end' },

   -- ╟─ Start of line ──╭───────╮── "for" ─╢
   --                    ╰─ WSP ─╯
   { '^%s*for', 'end' },

   -- ╟─ "function" ──╭───────╮── "(" ─╢
   --                 ╰─ WSP ─╯
   { 'function%s*%(', 'end' }, -- 'function(' or 'function ('

   {  '{', '}' },
   { '%(', ')' }, -- % to escape lua pattern char
   { '%[', ']' }, -- % to escape lua pattern char
},

image

image

The comment substring in foldtext is correctly handled on close elements adding.

image

image

If process_comment_signs = 'spaces' is set, the output will be

image

Setup for particular filetype

This plugin provides two setup functions.

  1. The first one setup configuration which will be used for all filetypes for which you doesn't set their own configuration.

    require('pretty-fold').setup(config: table)
    
  2. The second one allows to setup filetype specific configuration:

    require('pretty-fold').ft_setup(filtype: string, config: table)
    

Example of ready to use foldtext configuration only for lua files

require('pretty-fold').ft_setup('lua', {
   matchup_patterns = {
      { '^%s*do$', 'end' }, -- do ... end blocks
      { '^%s*if', 'end' },  -- if ... end
      { '^%s*for', 'end' }, -- for
      { 'function%s*%(', 'end' }, -- 'function( or 'function (''
      {  '{', '}' },
      { '%(', ')' }, -- % to escape lua pattern char
      { '%[', ']' }, -- % to escape lua pattern char
   },
}

ft_ignore options

default: { 'neorg' }

Filetypes to be ignored.

ft_ignore is a unique option. It exists only in a single copy for all global and filetype specific configurations. You can pass it in any function (setup() or ft_setup()) and all this values will be collected in this one single value.

Foldmethod specific configuration

The pretty-fold.nvim plugin supports saparate configuration for different foldmethods. For this pass the configuration table for a particular foldmethod as a value to the key named after foldmethod.

You can also pass global configuration table for all foldmethods and tune only desired options in foldmethod specific config table. All options that don't have value in foldmethod config table will be taken from global config table.

Example:

require('pretty-fold').setup({
    global = {...}, -- global config table for all foldmethods
    marker = { process_comment_signs = 'spaces' },
    expr   = { process_comment_signs = false },
})

Examples

require('pretty-fold').setup{
   keep_indentation = false,
   fill_char = '•',
   sections = {
      left = {
         '+', function() return string.rep('-', vim.v.foldlevel) end,
         ' ', 'number_of_folded_lines', ':', 'content',
      }
   }
}

image

require('pretty-fold').setup{
   keep_indentation = false,
   fill_char = '━',
   sections = {
      left = {
         '━ ', function() return string.rep('*', vim.v.foldlevel) end, ' ━┫', 'content', '┣'
      },
      right = {
         '┫ ', 'number_of_folded_lines', ': ', 'percentage', ' ┣━━',
      }
   }
}

image

Configuration for C++ to get nice foldtext for Doxygen comments

require('pretty-fold').ft_setup('cpp', {
   process_comment_signs = false,
   comment_signs = {
      '/**', -- C++ Doxygen comments
   },
   stop_words = {
      -- ╟─ "*" ──╭───────╮── "@brief" ──╭───────╮──╢
      --          ╰─ WSP ─╯              ╰─ WSP ─╯
      '%*%s*@brief%s*',
   },
})

image

image

Preview

I personally don't want to learn a new key combination to open fold preview. So I tried to create something that would feel natural.

With default keybindings the h and l are utilised. On first press of h key, if cursor is somewhere inside closed folded region, the preview will be shown. On second press the preview will be closed and fold will be opened. The l key, with opened preview, will close it and open fold. In all other cases this keys will working as usual.

A preview window also will be closed on any cursor move, changing mode, or buffer leaving.

To enable this feature call

require('pretty-fold.preview').setup()

Configuration

Available settngs with default values:

config = {
   default_keybindings = true, -- Set to false to disable default keybindings

   -- 'none', "single", "double", "rounded", "solid", 'shadow' or table
   -- For explanation see: :help nvim_open_win()
   border = {' ', '', ' ', ' ', ' ', ' ', ' ', ' '},
}

Custom keymaps

If you would like to setup your custom keymapings, there are next functions in

require('pretty-fold.preview').mapping

table for this. They are meant to be used with nvim-keymap-amend plugin so read its documentation for more info how to use them.

  • show_close_preview_open_fold(original) — show preview when cursor is inside fold. If preview is already shown, close preview and open fold. Otherway execute original mapping.

  • close_preview_open_fold(original) — close preview (if opened) and open fold. Otherway execute original mapping.

  • close_preview(original) — close preview (if opened) and execute original mapping.

  • close_preview_without_defer(original) — the same as previous, but close preview without defer.

For example here are original key mappings:

local keymap_amend = require('keymap-amend')
local mapping = require('pretty-fold.preview').mapping
keymap_amend('n', 'h',  mapping.show_close_preview_open_fold)
keymap_amend('n', 'l',  mapping.close_preview_open_fold)
keymap_amend('n', 'zo', mapping.close_preview)
keymap_amend('n', 'zO', mapping.close_preview)
keymap_amend('n', 'zc', mapping.close_preview_without_defer)

Additional information

Check 'fillchars' option. From lua it can be set the next way:

vim.opt.fillchars:append('fold:•')