Popfix Save

Neovim lua API for highly extensible popup window

Project README

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

  • Neovim nightly

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

  • Popup

    A simple popup list of elements.

    Rendering modes:

    • Split (In a down split)
    • Editor (Relative to neovim editor window)
    • Cursor (On the cursor itself)
    Click to see all screenshots
  • Preview Popup

    A popup list of elements with a preview window that can be configured by user.

    Rendering modes:

    • Split (In a down split)
    • Editor (Relative to neovim editor window)
    Click to see all screenshots

    (Preview window doesn't have cursor mode currently. This is because I don't have great idea about what it should look like. Make a PR if you have any good idea.)

  • Text

    A simple prompt window where user can enter some text as input.

    Rendering modes:

    • Editor (Relative to neovim editor window)
    • Cursor (On cursor itself)
    Click to see all screenshots

    (Currently working on split mode)

  • Prompt Popup

    A popup list of elements with a prompt having fuzzy finding capabilities by default.

    Rendering modes:

    • Split (In a down split)
    • Editor (Relative to neovim editor window)
    • Cursor (On the cursor itself)
    Click to see all screenshots
  • Prompt Preview Popup

    A popup list of elements with a prompt and preview window having fuzzy finding capabilities by default.

    Rendering modes:

    • Split (In a down split)
    • Editor (Relative to neovim editor window)
    Click to see all screenshots

    (Preview window doesn't have cursor mode currently. This is because I don't have great idea about what it should look like. Make a PR if you have any good idea.)

Components

Popfix contains following components to build the UI:

  • List
  • Preview
  • Prompt
  • Sorter
  • Fuzzy Engine

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):

  • border (boolean): If border should be there around corresponding window.
  • border_chars (table): border chars to be used for borders. See example for border_chars syntax.
  • title (string): title of window.
  • numbering (boolean): whether vim line number should be displayed.
  • highlight (string): HighlightGroup that would decide background and foreground of window

list also provides some additional attributes:

  • selection_highlight (string) : HighlightGroup that decides the appearance of selected list item.
  • matching_highlight (string) : HighlightGroup that decides the appearance of matching characters wrt to current prompt text.

preview also provides some additional attributes:

  • type (string) (not optional): Defines the type of preview window to render. Supported types:
    • terminal: Preview window would be a terminal window.
    • text: Preview window would display some text.
    • buffer: Preview window is an existing neovim buffer.
  • preview_highlight (string) : HighlightGroup that decides the appearance of highlighted line of preview window. (Not applicable in terminal type)

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

  • prompt_text (string): Prompt command text
  • init_text (string): Initial text that is written in prompt when window is opened.
  • prompt_highlight (string) : HighlightGroup that decides the appearance of prompt command (Eg: 'Command> ').

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:

  • Split
  • Editor
  • Cursor

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:

  • select [optional] (Called when selection is changed in list)
  • close [optional] (Called when popup is closed)
  • on_job_complete [optional] (Called when command provided completes its execution)

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:

  • terminal

    Lua table with format:

    
    cmd = 'command here',
    cwd = 'directory from which this command should be launched.'
    
    
  • text

    
    data = {'string', 'array'}, -- A string array
    line = 4 -- Line to be highlighted [optional]
    
    
  • buffer

    
    bufnr = 0, -- Current buffer is represented by 0
    line = 4 -- Line to be highlighted [optional]
    
    

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

  • set_data

    Syntax: set_data(data)

    @param data -> table : new data for resource.

  • get_current_selection

    Syntax: get_current_selection() : index, line

    @return index -> integer : represents currently selected index

    @return line -> string : represents currently selected string

  • set_prompt_text

    Syntax: set_prompt_text(text)

    @param text -> string : text to set to new prompt

  • select_next

    Syntax: select_next(callback)

    @param callback -> function : callback is called after selection change is done. Callback is a function with syntax:

    unction(index, line)
    nd
    

    If preview is enabled callback is expected to return appropriate data for preview according to preview type. (See callbacks section for appropriate data for preview)

  • select_prev

    Syntax: select_prev(callabck)

    @param callback -> function : same as select_next's callback

  • close

    Syntax: close(callback)

    @param callback -> function : callback is called when window is closed properly. Callback is a function with syntax:

    unction(index, line)
    

Sorter

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

For using builtin sorters:

  • fzy-native

    equire'popfix.sorter'.new_fzy_native_sorter(case_sensitive)
    
  • fzy (default)

    equire'popfix.sorter'.new_fzy_sorter(case_sensitive)
    

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:

  • SingleExecutionEngine (default)

    Executes submitted job one time and sorts the whole thing when prompt change.

    equire'popfix.fuzzy_enigne'.new_SingleExecutionEngine()
    
  • RepeatedExecutionEngine

    Executes the submitted job (with formatted command) everytime prompt is changed and displays the output as result. It only uses the highlighting part of sorter.

    Because command need to be formatted, it should be given in special format string. For example: rg --vimgrep %s . This %s will be replaced with current prompt text while running the job. So, be careful while entering data in opts.

    equire'popfix.fuzzy_enigne'.new_RepeatedExecutionEngine()
    

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

  • data (data provided while calling new function of popfix)
  • manager (render manger)
  • sorter (sorter class)
  • currentPromptText: currentPromptText during intialisation

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

  • add(line, index, indexForUsers) : add function adds the line to manager queue for rendering. index represents the line number at which line should be displayed in list. indexForUsers represents the index value with which select callbacks, or close callback would be called for the line.

  • clear() : clear the list's UI.

Some plugins built upon popfix

Open Source Agenda is not affiliated with "Popfix" Project. README Source: RishabhRD/popfix
Stars
85
Open Issues
7
Last Commit
1 year ago
Repository

Open Source Agenda Badge

Open Source Agenda Rating