Compare commits

...

14 Commits

Author SHA1 Message Date
1dec350d04
Updated context_manager/init.lua 2023-01-13 16:48:06 +00:00
6e3e7f9d2d
Moved presser/ -> lua/presser/
Created new director lua/ and moved presser/ into the subdirectory.
This is to align with other standard developments of Neovim extensions.
2023-01-13 16:40:43 +00:00
6b63ea6446
Added streamer.lua
Steamers are akin to that of Telescope's Pickers. A "steamer" is the
class structure used by builtins to create new windows/buffers for the
user to interact with. This code was simply migrated across from
presser@new and refactored to adopt a class-like OOP paradigm.

steamers@new is a wrapper function accessed by builtins to invoke the
class constructor Steamers@new. Allows to greater control and
flexibility of window/buffer options.
2023-01-13 16:29:21 +00:00
e1b2cb5de8
Added presser/builtins
Module is responsible for implementing a lazy-loader for any new tool
which is defined in table@modules. This function makes use of a
lazy-loading strategy (needs citation).
2023-01-13 16:25:56 +00:00
b14fadee45
Added builtins/__modules.lua
Script defines all tools available by presser.nvim, which are
lazy-loaded and executed when called by the user.

Function presser@find_replace has undergone major refactorisation:
	- Keymaps have been updated to respect new codebase structure.
	- A new OOP implementation is being experimented for constructing
	  windows, giving future flexibility. Consult presser/steamers.lua.
	- Function now calls the class@streamers and invokes the
	  streamers@new() method. This constructs the windows/buffers which
	  was originally done via call to presser@new().
	- This function is NOT directly accessed, but via proxy
	  table@builtins which lazy-loads function.

Any new tools which are created for presser are to be defined in this
file and lazy-loaded. Module may be renamed in future for theming
consistency.
2023-01-13 16:18:06 +00:00
028b34bdda
Added new actions module
All functions originally defined in presser/init.lua and part of
table@presser which were related to performing a specific action,
have be migrated to this file.

Function definitions have remained the same
	- presser@start_buffer has been renamed
	  actions@buf_put_cursor_at() to allow for more generic
	  use-cases which the function name did not imply. Call this
	  function to place the cursor into any buffer in the GCM by its
	  label name.
2023-01-13 16:13:21 +00:00
7a126af17a
Refactored main init.lua file
File has undergone refactor to reduce overall expected responsibility
which was temporary for kickstarting the extension.

The presser/init.lua script will now, and only now, be responsible for
handling and setting up configuration of presser itself (extensibility
to be concluded in future). Now, the script defines presser@setup()
which is to be called by a user in their Neovim config script. This
function currently defines one user command which allows for executing
the builtins@find_replace.

Most functions which were defined in table@presser have been migrated to
a new location presser/actions, defined in table@actions.

The function definition presser@find_replace has been migrated to
presser.builtins (in builtins@find_replace).

Consult other commits related to migrations for more info about specific
refactors.
2023-01-13 16:04:04 +00:00
686dbd9ae4
Upated context_manager
Added a getter function to return the current state of the global
context manager.
2023-01-13 16:02:11 +00:00
7908c64c00
Moved to presser subdirectory 2023-01-10 22:34:05 +00:00
d1ab26196a
Updated presser/utils.lua
Updated M@clean_buf. If the type is not string or is nil, return empty string.
2023-01-10 22:33:45 +00:00
4d40f43a73
Updated presser/init.lua
File has been moved to subdirectory.
Note: TBR = To Be Refactored/Restructured.

- changed `ctx` to `gcm` as import for context manager module.
	- gcm/GCM = global context manager
	- all associations with this variable have been updated
	  respectfully.
- func@new is no longer responsible for creating a new context in the
  GCM. This is now handled per built-in.
	- call to gcm@update now respects newly modified GCM structure.
- Added presser@move_next_buffer:
	- temporary function to experiment moving between buffers which
	  are marked as `allowed = true` in the GCM.
	- TBR.
- Added func@read_buffer:
	- reads the contents of all buffers in the GCM.
	- returns a table storing all collected buffer contents.
	- TBR.
- Added presser@execute:
	- temporary function which calls func@read_buffer and performs a
	  vim substitute as per the built-in presser@find_replace.
	 - TBR.
- Added func@start_buffer:
	- place the cursor in an initial buffer by name.
	- TBR. Should be func@put_cursor_in_buf (or similar).
- Updated keymap to store more keys for experimenting.
- Calculate the centre of the current window height (should check for
  split windows in future, or determine terminal size).
- Changed ordering of window creation. Windows created in order they are
  rendered.
2023-01-10 22:17:30 +00:00
a66d9ae62f
Updated context_manager/init.lua
- Added diagram for global context manager (GCM) structure.
- Removed local func@get_ctx_head (unused).
- Updated func@M.update:
	- function takes 2 parameter arguments (see docstring).
	- constructs new record for the given context in the GCM to keep
	  record of a newly constructed window buffer.
	- function updates the GCM with the new record.
- Updated func@M.flush:
	- handles for new structure of records in the GCM.
	- see docstring for info.
2023-01-10 22:02:16 +00:00
8f88af8d69
Renamed modules
Files were moved from top-level space into presser sub-directory.
2023-01-09 19:46:12 +00:00
90b57ed4b1
Added context manager module
Added a context manager for handling and managing windows/buffers
constructed by built-ins. This was handled by the `new()` function in
presser/init.lua but is now a dedicated module.
2023-01-09 19:44:44 +00:00
9 changed files with 404 additions and 132 deletions

130
init.lua
View File

@ -1,130 +0,0 @@
local popup = require'plenary.popup'
local utils = require'presser.utils'
local api = vim.api
local presser = {} -- list of functions to be exported
local g = vim.g
g.presser_buf_ctx = {} -- setup a new global context manager for windows/buffers
-- :@Dev: close all Presser windows that may be open for all contexts
presser.close = function ()
for _type, ctx in pairs( g.presser_buf_ctx ) do
for _, win_id in pairs( ctx ) do
api.nvim_win_close( win_id, true ) -- buffer contents are irrelevant in this context to save
end
end
g.presser_buf_ctx = {} -- clear the global context manager
end
-- @Description: Construct a new instance of a window and buffer using the 'plenary.popup' module.
-- @Params:
-- + `_type` ~ string denoting which context manager is owner of the window.
-- + `placeholder` ~ string allowing for placeholder text to be placed within the buffer when constructed.
-- + `opts` ~ a table of additional options to be provided when constructing the window.
-- @Returns: nil. Constructs a new window and stores the window's ID in a global table.
--
-- @Dev: This function is to be as generic for creating a window whilst allowing for as much customisation over the
-- window/buffer which are to be constructed. Users are to access this function so is not part of the module export.
-- However, it's used by all internal built-ins which are meant to be used by users. Consider a class constructor in
-- C++, this function is akin to that behaviour and is responsible for constructing the window/buffer and ensuring it
-- can be tracked by Vim itself.
local new = function ( _type, placeholder, opts )
-- create new manager for handling windows in the
local manager = g.presser_buf_ctx
-- create specific context manager if it doesn't exist
if not manager[_type] then
manager[_type] = {}
g.presser_buf_ctx = manager
end
local opts = opts or {}
local placeholder = placeholder or ""
local buf_opts = {
minwidth = 80, -- getwin_w() / 2 - ((getwin_w() % 2) / 2),
borderchars = { "", "", "", "", "", "", "", "" },
}
if opts.window then
for k,v in pairs( opts.window ) do
buf_opts[k] = v
end
end
local obj = popup.create( placeholder, buf_opts )
table.insert( manager[_type], obj )
g.presser_buf_ctx = manager
-- :@Dev: handle for keybindings (TODO: any future stuff below here once window is made)
local buf_id = function ()
return api.nvim_win_get_buf(obj)
end
-- handle for key bindings
if opts.keybinds then
for mode, mode_map in pairs( opts.keybinds ) do
mode = string.lower(mode)
for key_bind, key_action in pairs(mode_map) do
local key_bind = api.nvim_replace_termcodes(key_bind, true, false, true),
api.nvim_buf_set_keymap(buf_id(), mode, key_bind, key_action,
{ noremap = true, silent = true }
)
end
end
end
end
-- @Description: Find and replace words within the current buffer.
-- @Params:
-- @Returns: nil.
--
-- @Dev: function is responsible to creating all required buffers to allow full user interaction.
--
-- @Future: implementation may allow for greater user customisation similar to what's found with
-- extensions such as Telescope. For now, it should provide a concrete UI for purpose of design.
function presser.find_replace()
-- define the context which these windows will belong to in the context manager.
local ctx = "find_replace"
local keymap = {
n = {
["<esc>"] = "<cmd>lua require'presser'.close()<CR>",
},
}
-- options for the title buffer of the built-in
local opts = {
window = {
line = 12,
border = false,
minwidth = 82,
padding = { 0, 1, 1, 1 }
},
keybinds = keymap,
}
local tab_title = new( ctx, "Find & Replace", opts )
-- options for the replace buffer
local opts = {
window = {
line = 18,
title = "Replace",
},
keybinds = keymap,
}
local replace = new( ctx, nil, opts )
-- modify options for find buffer
opts["window"]["line"] = 15
opts["window"]["title"] = "Find"
local find = new ( ctx, nil, opts )
api.nvim_feedkeys('A', 'n', false)
end
return presser

View File

@ -0,0 +1,72 @@
local gcm = require'presser.context_manager'
local utils = require'presser.utils'
local a = vim.api
local actions = {}
-- :@Dev: close all Presser windows that may be open for all contexts
actions.close = function ()
gcm.flush() -- invoke the context manager to flush the table
end
actions.move_next_buffer = function ()
local ctxs = 0
local c = gcm.get_global_context_manager()
local buf = a.nvim_get_current_buf()
for _, v in pairs( c ) do for idx, _ in pairs( v ) do ctxs = idx end end
for _, ctx_obj in pairs( c ) do
for idx, obj in pairs( ctx_obj ) do
if a.nvim_win_get_buf( obj.id ) == buf then
-- get the next window, and put cursor there
local s = idx
repeat
local win = ctx_obj[(idx % ctxs) + 1]
if win.allowed then
a.nvim_set_current_win( ctx_obj[(idx % ctxs) + 1].id )
break
end
idx = (idx % ctxs) + 1
until s == idx
end
end
end
end
local read_buffer = function ()
local c = vim.g.presser_buf_ctx
local b = {}
for _, ctx_obj in pairs( c ) do
for _, obj in pairs( ctx_obj ) do
if obj.allowed then
local buf = a.nvim_win_get_buf( obj.id )
table.insert(b, utils.clean_buf( a.nvim_buf_get_lines( buf, 0, -1, false )[1] ))
end
end
end
return b
end
actions.execute = function ()
local result = read_buffer()
actions.close()
local cmd = ":%s/" .. result[1] .. "/" .. result[2] .. "/g"
a.nvim_exec( cmd, false )
end
actions.buf_put_curser_at = function ( label )
for _, ctx_obj in pairs( vim.g.presser_buf_ctx ) do
for _, record in pairs( ctx_obj ) do
if record.what == label then
a.nvim_set_current_win( record.id )
end
end
end
end
return actions

View File

@ -0,0 +1,83 @@
local gcm = require'presser.context_manager'
local steamers = require'presser.steamers'
local actions = require'presser.actions'
local a = vim.api
local modules = {}
-- @Description: Find and replace words within the current buffer.
-- @Params:
-- @Returns: nil.
--
-- @Dev: function is responsible to creating all required buffers to allow full user interaction.
--
-- @Future: implementation may allow for greater user customisation similar to what's found with
-- extensions such as Telescope. For now, it should provide a concrete UI for purpose of design.
modules.find_replace = function ()
-- define the context which these windows will belong to in the context manager.
local ctx = "find_replace"
gcm.create( ctx )
local keymap = {
n = {
["<esc>"] = "<cmd>lua require'presser.actions'.close()<CR>",
["<A-a>"] = "<cmd>lua require'presser.actions'.execute()<CR>",
},
i = {
["<down>"] = "<cmd>lua require'presser.actions'.move_next_buffer()<CR>",
["<A-a>"] = "<cmd>lua require'presser.actions'.execute()<CR>",
}
}
-- get the centre of the current buffer
local c_cols = math.floor( a.nvim_win_get_width(0) / 2 )
local c_lines = math.floor( a.nvim_win_get_height(0) / 2 )
-- create a title buffer for the steamer
steamers
.new( ctx, {
label = "presser_title",
steamer = {
placeholder = "Find & Replace",
allowed = false,
window = {
line = c_lines - 3,
border = false,
minwidth = 82,
padding = { 0, 1, 1, 1 }
}
}
} )
-- create a new buffer for find pattern
steamers
.new ( ctx, {
label = "find_buf",
steamer = {
window = {
line = c_lines,
title = "Find",
},
keybinds = keymap,
}
} )
-- create a new buffer for replace pattern
steamers
.new( ctx, {
label = "replace_buf",
steamer = {
window = {
line = c_lines + 3,
title = "Replace",
},
keybinds = keymap,
}
} )
local result = actions.buf_put_curser_at( "find_buf" )
a.nvim_feedkeys('A', 'n', false)
end
return modules

View File

@ -0,0 +1,16 @@
local builtins = {}
local function require_on_exported_call (require_path)
return setmetatable({}, {
__index = function(_, k)
return function(...)
return require(require_path)[k](...)
end
end,
})
end
builtins.find_replace = require_on_exported_call("presser.builtins.__modules").find_replace
return builtins

View File

@ -0,0 +1,120 @@
local a = vim.api
local g = vim.g
-- @Dev: The following table layout is the structure of contexts stored in the global manager.
--
-- vim.g.presser_buf_ctx = {
-- <ctx> = {
-- <int> {
-- id = <win_id>,
-- what = "Name of window" | id,
-- allowed = true | false,
-- },
-- ...
-- }
-- }
--
-- setup a new global context manager for windows/buffers
g.presser_buf_ctx = g.presser_buf_ctx or {}
local M = {}
-- @TODO: getters which might be useful
-- + get_win_by_name
-- + get_idx_from_win_id
-- + get_next_allowed_win
-- + get_prev_allowed_win
-- @Description: Create a new context in the global context manager if it doesn't already exist.
-- @Params:
-- + `ctx` ~ A string which represents the name of the new context to be created.
-- @Returns: nil. Updates the global context manager with a new context.
--
-- @Dev: note that this function shouldn't be responsible for multiple contexts existing in the global manager. Whilst
-- it's possible that multiple contexts could exist, it would imply multiple UIs open at once, so unideal.
M.create = function ( ctx )
local c = g.presser_buf_ctx
-- create specific context manager if it doesn't exist
if not c[ctx] then
c[ctx] = {}
g.presser_buf_ctx = c
end
end
-- @Description: Update a context within the global context manager with a new buffer record. If the context doesn't
-- exist, this function will guarantee that it does. If an ID for the window to be recorded in the context manager isn't
-- provided, then this function errors. The table passed to `obj` must contain the key `id` for the function to succeed.
-- The fields are as followed:
-- + `id` ~ A number which is given when a new buffer window is constructed.
-- + `what` ~ A string which gives the ID meaning; a label for the window (not related to buffer).
-- + `allowed` ~ A boolean value which tells the context manager if the cursor is allowed in a window (virtually
-- disables the buffer).
--
-- `id` is the unique value to the record. Whilst `what` is a guaranteed value, its uniqueness isn't guaranteed in a
-- given context. Since `what` can be used to fetch the associated `id` of the record, it should be unique from other
-- records to prevent conflicts within a context to multiple window IDs.
-- @Params:
-- + `ctx` ~ A string representing the context which the record will be added to in the global context manager.
-- + `obj` ~ A table holding one argument and two optionals.
-- @Returns: bool.
--
-- @Dev: this function should require proper error handling to alert a dev when something really should be guaranteed,
-- such as ID. (bool is fine to return, so perhaps error handling should be considered here for benefits).
M.update = function ( ctx, obj )
-- ensure that the context exists
-- if not g.presser_buf_ctx[ctx] then
-- M.create( ctx )
-- end
if not obj.id then
print("[ERROR - context_manager] cannot allocate window. No ID was provided.")
return false
end
if obj.allowed == nil then
obj.allowed = true
end
-- construct new buffer record for context
local data = {
id = obj.id,
what = obj.what or obj.id,
allowed = obj.allowed,
}
-- read the global context buffer and add new buffer to context
local c = g.presser_buf_ctx
table.insert(c[ctx], data)
g.presser_buf_ctx = c
return true
end
-- @Description: Flush the contents of the global context manager and destroy all associated windows and buffers to a
-- context. Any information stored in the buffers is not saved during this process. Any such buffer contents should be
-- handled before calling to flush.
-- @Params: nil.
-- @Returns: nil. Destroys all windows/buffers and clears the global context manager
--
-- @Dev: This is why it's important to guarantee that the IDs are stored in the context manager. They are required here
-- to close the windows using the vim API calls, and force closing them too.
M.flush = function ()
for _, ctx_tbl in pairs( g.presser_buf_ctx ) do
for _, data in pairs( ctx_tbl ) do
--print("Deleting:", _, data.id)
a.nvim_win_close( data.id, true ) -- buffer contents are irrelevant in this context to save
end
end
g.presser_buf_ctx = {} -- clear the global context manager
end
M.get_global_context_manager = function ()
return g.presser_buf_ctx
end
return M

34
lua/presser/init.lua Normal file
View File

@ -0,0 +1,34 @@
local builtins = require'presser.builtins'
local a = vim.api
local presser = {} -- list of functions to be exported
presser.setup = function ( opts )
local opts = opts or {}
-- @Dev: TODO - construct setup function for configs
--
--map("n", "<Leader>h", "<cmd>lua require'presser'.find_replace()<CR>") -- global replace with last buffer item
a.nvim_create_user_command(
'Presser',
function ( o )
local args = {}
for arg in string.gmatch( o.args, "%S+" ) do
table.insert( args, arg )
end
builtins[args[1]]()
end,
{
nargs = 1,
complete = function (ArgLead, CmdLine, CursorPos)
return { 'find_replace' }
end,
}
)
end
return presser

View File

78
lua/presser/steamers.lua Normal file
View File

@ -0,0 +1,78 @@
local popup = require'plenary.popup'
local gcm = require'presser.context_manager'
local a = vim.api
local steamers = {}
local Steamer = {}
Steamer.__index = Steamer
-- @Description: Construct a new instance of a window and buffer using the 'plenary.popup' module.
-- @Params:
-- + `_type` ~ string denoting which context manager is owner of the window.
-- + `placeholder` ~ string allowing for placeholder text to be placed within the buffer when constructed.
-- + `opts` ~ a table of additional options to be provided when constructing the window.
-- @Returns: nil. Constructs a new window and stores the window's ID in a global table.
--
-- @Dev: This function is to be as generic for creating a window whilst allowing for as much customisation over the
-- window/buffer which are to be constructed. Users are to access this function so is not part of the module export.
-- However, it's used by all internal built-ins which are meant to be used by users. Consider a class constructor in
-- C++, this function is akin to that behaviour and is responsible for constructing the window/buffer and ensuring it
-- can be tracked by Vim itself.
function Steamer:new ( _ctx, label, opts )
local opts = opts or {}
if not opts.placeholder then
opts["placeholder"] = ""
end
local buf_opts = {
minwidth = 80, -- getwin_w() / 2 - ((getwin_w() % 2) / 2),
borderchars = { "", "", "", "", "", "", "", "" },
}
if opts.window then
for k,v in pairs( opts.window ) do
buf_opts[k] = v
end
end
print("building window: ", opts.placeholder, buf_opts )
local obj = popup.create( opts.placeholder, buf_opts )
gcm.update( _ctx, { id = obj, what = label, allowed = opts.allowed } )
-- :TODO: call update function in context manager
-- table.insert( manager[_type], obj )
-- g.presser_buf_ctx = manager
-- :@Dev: handle for keybindings (TODO: any future stuff below here once window is made)
local buf_id = function ()
return a.nvim_win_get_buf(obj)
end
-- handle for key bindings
if opts.keybinds then
for mode, mode_map in pairs( opts.keybinds ) do
mode = string.lower(mode)
for key_bind, key_action in pairs(mode_map) do
local key_bind = a.nvim_replace_termcodes(key_bind, true, false, true),
a.nvim_buf_set_keymap(buf_id(), mode, key_bind, key_action,
{ noremap = true, silent = true }
)
end
end
end
end
steamers.new = function ( ctx, opts )
-- error if the provided option is not type string
assert( type(opts.label) == "string", "Should be string, found type: " .. type(opts.label) )
-- @Dev: function can provide greater wrapper options in future.
return Steamer:new( ctx, opts.label, opts.steamer )
end
steamers._Steamer = Steamer
return steamers

View File

@ -28,8 +28,7 @@ end
-- :@dev: clean the text once it has been fetched from buffer -- :@dev: clean the text once it has been fetched from buffer
M.clean_buf = function ( text ) M.clean_buf = function ( text )
if not type(text) == "string" or text == nil then if not type(text) == "string" or text == nil then
print("[DEBUG - clean_buf] Given input is not of type string.") return ""
return
end end
return escape_chars( text ) return escape_chars( text )