RishabhRD / popfix

Neovim lua API for highly extensible popup window
83 stars 3 forks source link
fuzzy-engine fuzzy-finder gui lua neovim neovim-api neovim-gui neovim-plugin

popfix

popfix is a neovim API that helps plugin developers to write UI for their plugins easily. popfix internally handles UI rendering and its lifetime and makes plugin developers to focus much on plugin logic and less on UI.

popfix targets UI where either user have to give some input or user have to select something from a list of elements. Because these are most common operations most plugins need to do. However, popfix is very customizable and extensible that many of its default behaviour can be changed.

Also normal users can use this API to provide fancy UI for some common tasks.

Prerequisites

Install

Install with any plugin manager. For example with vim-plug

Plug 'RishabhRD/popfix'

For manual installation:

cd $HOME/.config/nvim/plugged
git clone --recurse-submodules https://github.com/RishabhRD/popfix

Installing fzy-native sorter

native-fzy-sorter is a very fast embedded C sorter. (Read sorter section) To install fzy-native-sorter:

cd <plugin-directory>
./install_fzy_native_sorter

UI possible with popfix

Components

Popfix contains following components to build the UI:

Because sorter and fuzzy engine defines how prompt popup and prompt preview popup would behave and hence are very extensible and customizable. Infact users can provide their own sorter and fuzzy engine.

Other components are UI components and their working is quite obvious and are very customizable in terms of look and behaviour.

API Description

How to invoke plugin

Example (in most descriptive way, infact many of these options are optional):

local border_chars = {
    TOP_LEFT = '┌',
    TOP_RIGHT = '┐',
    MID_HORIZONTAL = '─',
    MID_VERTICAL = '│',
    BOTTOM_LEFT = '└',
    BOTTOM_RIGHT = '┘',
}

local function select_callback(index, line)
    -- function job here
end

local function close_callback(index, line)
    -- function job here
end

local opts = {
    height = 40,
    width = 120,
    mode = 'editor',
    close_on_bufleave = true,
    data = <data-table>, -- Read below how to provide this.
    keymaps = {
        i = {
            ['<Cr>'] = function(popup)
                popup:close(select_callback)
            end
        },
        n = {
            ['<Cr>'] = function(popup)
                popup:close(select_callback)
            end
        }
    }
    callbacks = {
        select = select_callback, -- automatically calls it when selection changes
        close = close_callback, -- automatically calls it when window closes.
    }
    list = {
        border = true,
        numbering = true,
        title = 'MyTitle',
        border_chars = border_chars,
        highlight = 'Normal',
        selection_highlight = 'Visual',
        matching_highlight = 'Identifier',
    },
    preview = {
        type = 'terminal'
        border = true,
        numbering = true,
        title = 'MyTitle',
        border_chars = border_chars,
        highlight = 'Normal',
        preview_highlight = 'Visual',
    },
    prompt = {
        border = true,
        numbering = true,
        title = 'MyTitle',
        border_chars = border_chars,
        highlight = 'Normal',
        prompt_highlight = 'Normal'
    },
    sorter = require'popfix.sorter'.new_fzy_native_sorter(true),
    fuzzyEngine = require'popfix.fuzzy_engine'.new_SingleExecutionEngine()
}

local popup = require'popfix':new(opts)

popup returned by the new function is the resource created by popfix. If everything works well, it returns the resource otherwise false.

Options description

list, preview, prompt, sorter, fuzzyEngine describes the components to render UI. (See Components section)

If sorter and fuzzyEngine is not provided, popfix provides a suitable defaults for it.

Popfix manipulates the UI on basis of list, preview and prompt options. So, for example, if only list is provided then a Popup would render. If list and preview both are provided then Preview Popup would render. Similarily, if list, preview and prompt are provided then a Prompt Preview Popup would render.

See UI possible with popfix section for reasonable combinations possible with popfix. Naming convention and options are non surprising.

list, preview and prompt have some common attributes (all of them are optional):

list also provides some additional attributes:

preview also provides some additional attributes:

prompt also provides some additional attributes (these are optional):

height [optional] [int]

Height of rendered window. (Height would be divided for components internally)

width [optional] [int]

Overall width of rendered window. (Width would be divided for components internally)

close_on_bufleave [optional] [boolean]

Window should be closed when user leaves the buffer. (Default: false)

mode [string]

Defines the rendering mode:

data [table]

If you wanna display some list of string from lua itself then this table is an array of strings.

Example: data = {"Hello", "World"}

If you want to display the output of some command then, this table should be of format:

data = {
    cmd = 'command here',
    cwd = 'directory from which this command should be launched.',
}

keymaps [optional] [table]

Keymaps are expressed as lua tables. Currently only normal mode and insert mode keymappings are supported. n represents normal mode, i represents the insert mode.

Any lua function or string can be provided as value to key. Lua function have syntax:

function(popup)
end

where popup is the resource that was created by new function.

For example:

{
    i = {
        ['<C-n>'] = function(popup)
            popup:select_next()
        end
    },
    n = {
        ['q'] = ':q!'
    }
}

callbacks [optional]

API provides 2 callbacks on which plugins can react:

See example for callbacks syntax.

The values for these callbacks are a function with syntax (excpet on_job_complete):

function(index, line)
    -- process and return data for preview if there.
end

index represents the index of currently selected element, i.e., its position while adding.

line represents the string of line of currently selected element.

If preview is enabled then these function should return value for preview based on preview type.

For different preview types return value should be:

Methods exported by popfix resource

Every resource of popfix created by require'popfix'.new(opts) exports some methods that can be called with syntax: resource:func(param)

where resource is the resource returned by new function, func is the method which is being called and param is parameter to the function.

The table shows the function exported by different resources:

Function Popup Preview Popup Text Prompt Popup Prompt Preview Popup
set_data :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
get_current_selection :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
set_prompt_text :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
select_next :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
select_prev :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
close :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:

If you feel any useful method is missing here, please raise a issue or submit a PR:smiley:.

Method description

Sorter provides the sorting algorithm, filter algorithm and highlighting algorithm.

For using builtin sorters:

case_sensitive is boolean that indicates if sorting algorithm is case_sensitive or not.

You can also create new sorters:

local sorter = require'popfix.sorter'.new{
    sorting_function = function(prompt_text, comparing_text, case_sensitive)
        -- scoring logic here
    end,
    filter_function = function(prompt_text, comparing_text, case_sensitive)
        -- scoring logic here
    end,
    highlighting_function = function(prompt_text, comparing_text, case_sensitive)
        -- scoring logic here
    end
}

filter_function should return a boolean indicating comparing_text should be listed in results or not.

sorting_function should return a score. Higher score value means better score.

highlighting_function should return array of integers representing columns need to be highlighted for comparing_text. Column should be 0 indexed. eg: {0, 3, 5, 9}

Fuzzy Engine

Fuzzy Engine schedules the job for fuzzy search. This actually sorts the result according to sorter's algorithm and then send it to a manager using add method of manager.

Popfix provides 2 built-in fuzzy engines:

You can also create new fuzzy engines. For creating a new fuzzy engine:

require'popfix.fuzzy_engine':new{
    run = function(opts)
    end,
    close = function()
    end
}

run is the function that will start fuzzy engine. It accepts an opts parameter that has 4 fields

manager is the utility that takes cares of actual rendering. It has 2 methods:

Some plugins built upon popfix