From 543c72a2f470dfff61552f942d97e121c584983b Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Mon, 9 Jan 2023 19:44:44 +0000 Subject: [PATCH 01/14] 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. --- presser/context_manager/init.lua | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 presser/context_manager/init.lua diff --git a/presser/context_manager/init.lua b/presser/context_manager/init.lua new file mode 100644 index 0000000..ed74d37 --- /dev/null +++ b/presser/context_manager/init.lua @@ -0,0 +1,45 @@ +local a = vim.api +local g = vim.g + +-- setup a new global context manager for windows/buffers +g.presser_buf_ctx = g.presser_buf_ctx or {} + + +local M = {} + +local get_ctx_head = function ( ctx ) + return g.presser_buf_ctx[ctx][1] +end + +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 + +-- :TODO: restructure function to allow for new data structure (see todo file) +M.update = function ( ctx, data ) + local c = g.presser_buf_ctx + -- ensure that the context exists + if not c[ctx] then + M.create( ctx ) + end + + table.insert(c[ctx], data) + g.presser_buf_ctx = c +end + +M.flush = function () + for ctx, ctx_tbl in pairs( g.presser_buf_ctx ) do + for _, win_id in pairs( ctx_tbl ) do + a.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 + +return M From 3724426d33fd1fd8c26bf468d189b102acb2631b Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Mon, 9 Jan 2023 19:46:12 +0000 Subject: [PATCH 02/14] Renamed modules Files were moved from top-level space into presser sub-directory. --- presser/init.lua | 124 ++++++++++++++++++++++++++++++++++++++++++++++ presser/utils.lua | 40 +++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 presser/init.lua create mode 100644 presser/utils.lua diff --git a/presser/init.lua b/presser/init.lua new file mode 100644 index 0000000..6b49ec0 --- /dev/null +++ b/presser/init.lua @@ -0,0 +1,124 @@ +local popup = require'plenary.popup' +local utils = require'presser.utils' +local ctx = require('presser.context_manager') + +local api = vim.api + +local presser = {} -- list of functions to be exported + + +-- :@Dev: close all Presser windows that may be open for all contexts +presser.close = function () + ctx.flush() -- invoke the context manager to flush the table +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 ( _ctx, label, opts ) + -- create new context in the manager if it doesn't already exist + ctx.create( _ctx ) + + 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 + + local obj = popup.create( opts.placeholder, buf_opts ) + ctx.update( _ctx, obj ) + -- :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 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 = { + [""] = "lua require'presser'.close()", + }, + } + + -- options for the title buffer of the built-in + local opts = { + placeholder = "Find & Replace", + window = { + line = 12, + border = false, + minwidth = 82, + padding = { 0, 1, 1, 1 } + }, + keybinds = keymap, + } + local tab_title = new( ctx, "presser_fr_title", opts ) + + -- options for the replace buffer + local opts = { + window = { + line = 18, + title = "Replace", + }, + keybinds = keymap, + } + local replace = new( ctx, "replace_buf", opts ) + + -- modify options for find buffer + opts["window"]["line"] = 15 + opts["window"]["title"] = "Find" + local find = new ( ctx, "find_buf", opts ) + + api.nvim_feedkeys('A', 'n', false) +end + + +return presser diff --git a/presser/utils.lua b/presser/utils.lua new file mode 100644 index 0000000..98573fc --- /dev/null +++ b/presser/utils.lua @@ -0,0 +1,40 @@ +local M = {} + +M.iter_table_dump = function ( arr ) + for k, v in pairs(arr) do + print("Table:", k, v) + end +end + +local escape_chars = function ( text ) + return string.gsub(text, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", { + ["\\"] = "\\\\", + ["-"] = "\\-", + ["("] = "\\(", + [")"] = "\\)", + ["["] = "\\[", + ["]"] = "\\]", + ["{"] = "\\{", + ["}"] = "\\}", + ["?"] = "\\?", + ["+"] = "\\+", + ["*"] = "\\*", + ["^"] = "\\^", + ["$"] = "\\$", + ["."] = "\\.", + }) +end + +-- :@dev: clean the text once it has been fetched from buffer +M.clean_buf = function ( text ) + if not type(text) == "string" or text == nil then + print("[DEBUG - clean_buf] Given input is not of type string.") + return + end + + return escape_chars( text ) + :match( "^%s*(.-)%s*$" ) +end + + +return M From 4049e5c2d0e449afb3d947a1b7e6c187eadc37e8 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Tue, 10 Jan 2023 22:02:16 +0000 Subject: [PATCH 03/14] 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. --- presser/context_manager/init.lua | 94 ++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/presser/context_manager/init.lua b/presser/context_manager/init.lua index ed74d37..37adee6 100644 --- a/presser/context_manager/init.lua +++ b/presser/context_manager/init.lua @@ -1,16 +1,39 @@ 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 = { +-- = { +-- { +-- 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 = {} -local get_ctx_head = function ( ctx ) - return g.presser_buf_ctx[ctx][1] -end +-- @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 @@ -20,26 +43,75 @@ M.create = function ( ctx ) end end --- :TODO: restructure function to allow for new data structure (see todo file) -M.update = function ( ctx, data ) - local c = g.presser_buf_ctx + +-- @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 c[ctx] then - M.create( ctx ) +-- 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, ctx_tbl in pairs( g.presser_buf_ctx ) do - for _, win_id in pairs( ctx_tbl ) do - a.nvim_win_close( win_id, true ) -- buffer contents are irrelevant in this context to save + 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 + return M From d96c5b20281a3a2c60944e2727a6083ccab4fa4a Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Tue, 10 Jan 2023 22:17:30 +0000 Subject: [PATCH 04/14] 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. --- presser/init.lua | 104 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/presser/init.lua b/presser/init.lua index 6b49ec0..41632fe 100644 --- a/presser/init.lua +++ b/presser/init.lua @@ -1,6 +1,6 @@ local popup = require'plenary.popup' local utils = require'presser.utils' -local ctx = require('presser.context_manager') +local gcm = require('presser.context_manager') local api = vim.api @@ -9,7 +9,7 @@ local presser = {} -- list of functions to be exported -- :@Dev: close all Presser windows that may be open for all contexts presser.close = function () - ctx.flush() -- invoke the context manager to flush the table + gcm.flush() -- invoke the context manager to flush the table end @@ -26,9 +26,6 @@ end -- 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 ( _ctx, label, opts ) - -- create new context in the manager if it doesn't already exist - ctx.create( _ctx ) - local opts = opts or {} if not opts.placeholder then opts["placeholder"] = "" @@ -46,7 +43,7 @@ local new = function ( _ctx, label, opts ) end local obj = popup.create( opts.placeholder, buf_opts ) - ctx.update( _ctx, obj ) + 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 @@ -71,6 +68,69 @@ local new = function ( _ctx, label, opts ) end end + +presser.move_next_buffer = function () + local ctxs = 0 + local c = vim.g.presser_buf_ctx + local buf = api.nvim_get_current_buf() + + for _, v in pairs( c ) do for idx, _ in pairs( v ) do ctxs = idx end end + + for ctx, ctx_obj in pairs( c ) do + for idx, obj in pairs( ctx_obj ) do + if api.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 + api.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, ctx_obj in pairs( c ) do + for idx, obj in pairs( ctx_obj ) do + if obj.allowed then + local buf = api.nvim_win_get_buf( obj.id ) + table.insert(b, utils.clean_buf( api.nvim_buf_get_lines( buf, 0, -1, false )[1] )) + end + end + end + + return b +end + +presser.execute = function () + local result = read_buffer() + presser.close() + + local cmd = ":%s/" .. result[1] .. "/" .. result[2] .. "/g" + api.nvim_exec( cmd, false ) +end + + +local start_buffer = function ( label ) + for ctx, ctx_obj in pairs( vim.g.presser_buf_ctx ) do + for idx, record in pairs( ctx_obj ) do + if record.what == label then + api.nvim_set_current_win( record.id ) + end + end + end +end + + -- @Description: Find and replace words within the current buffer. -- @Params: -- @Returns: nil. @@ -82,43 +142,57 @@ end function presser.find_replace() -- define the context which these windows will belong to in the context manager. local ctx = "find_replace" + gcm.create( ctx ) local keymap = { n = { [""] = "lua require'presser'.close()", + [""] = "lua require'presser'.execute()", }, + i = { + [""] = "lua require'presser'.move_next_buffer()", + [""] = "lua require'presser'.execute()", + } } + -- get the centre of the current buffer + local c_cols = math.floor( api.nvim_win_get_width(0) / 2 ) + local c_lines = math.floor( api.nvim_win_get_height(0) / 2 ) + +-- print(c_cols, c_lines) + -- options for the title buffer of the built-in local opts = { placeholder = "Find & Replace", + allowed = false, window = { - line = 12, + line = c_lines - 3, border = false, minwidth = 82, padding = { 0, 1, 1, 1 } }, - keybinds = keymap, } local tab_title = new( ctx, "presser_fr_title", opts ) -- options for the replace buffer local opts = { window = { - line = 18, - title = "Replace", + line = c_lines, + title = "Find", }, keybinds = keymap, } - local replace = new( ctx, "replace_buf", opts ) - - -- modify options for find buffer - opts["window"]["line"] = 15 - opts["window"]["title"] = "Find" local find = new ( ctx, "find_buf", opts ) + -- modify options for find buffer + opts["window"]["line"] = c_lines+3 + opts["window"]["title"] = "Replace" + local replace = new( ctx, "replace_buf", opts ) + + local result = start_buffer( "find_buf" ) api.nvim_feedkeys('A', 'n', false) end +--presser.find_replace() return presser From c498210a2f80cf5c9d75b676ceddf3bdf62059d5 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Tue, 10 Jan 2023 22:33:45 +0000 Subject: [PATCH 05/14] Updated presser/utils.lua Updated M@clean_buf. If the type is not string or is nil, return empty string. --- presser/utils.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/presser/utils.lua b/presser/utils.lua index 98573fc..13583c6 100644 --- a/presser/utils.lua +++ b/presser/utils.lua @@ -28,8 +28,7 @@ end -- :@dev: clean the text once it has been fetched from buffer M.clean_buf = function ( text ) if not type(text) == "string" or text == nil then - print("[DEBUG - clean_buf] Given input is not of type string.") - return + return "" end return escape_chars( text ) From 18f7e1d9bb81d25b85770d7aeb3e7316f971813a Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Tue, 10 Jan 2023 22:34:05 +0000 Subject: [PATCH 06/14] Moved to presser subdirectory --- init.lua | 130 ------------------------------------------------------ utils.lua | 40 ----------------- 2 files changed, 170 deletions(-) delete mode 100644 init.lua delete mode 100644 utils.lua diff --git a/init.lua b/init.lua deleted file mode 100644 index b93939f..0000000 --- a/init.lua +++ /dev/null @@ -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 = { - [""] = "lua require'presser'.close()", - }, - } - - -- 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 diff --git a/utils.lua b/utils.lua deleted file mode 100644 index 98573fc..0000000 --- a/utils.lua +++ /dev/null @@ -1,40 +0,0 @@ -local M = {} - -M.iter_table_dump = function ( arr ) - for k, v in pairs(arr) do - print("Table:", k, v) - end -end - -local escape_chars = function ( text ) - return string.gsub(text, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", { - ["\\"] = "\\\\", - ["-"] = "\\-", - ["("] = "\\(", - [")"] = "\\)", - ["["] = "\\[", - ["]"] = "\\]", - ["{"] = "\\{", - ["}"] = "\\}", - ["?"] = "\\?", - ["+"] = "\\+", - ["*"] = "\\*", - ["^"] = "\\^", - ["$"] = "\\$", - ["."] = "\\.", - }) -end - --- :@dev: clean the text once it has been fetched from buffer -M.clean_buf = function ( text ) - if not type(text) == "string" or text == nil then - print("[DEBUG - clean_buf] Given input is not of type string.") - return - end - - return escape_chars( text ) - :match( "^%s*(.-)%s*$" ) -end - - -return M From 865bb416092e2870a6841cfc471d825d37d5dc1d Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:02:11 +0000 Subject: [PATCH 07/14] Upated context_manager Added a getter function to return the current state of the global context manager. --- presser/context_manager/init.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/presser/context_manager/init.lua b/presser/context_manager/init.lua index 37adee6..634a9b5 100644 --- a/presser/context_manager/init.lua +++ b/presser/context_manager/init.lua @@ -103,7 +103,7 @@ end -- @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, ctx_tbl in pairs( g.presser_buf_ctx ) do + 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 @@ -113,5 +113,8 @@ M.flush = function () g.presser_buf_ctx = {} -- clear the global context manager end +M.get_global_context_manager = function () + return g.presser_buf_ctx +end return M From adf5b94d78bd73f2704276df37c19e181f6697c3 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:04:04 +0000 Subject: [PATCH 08/14] 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. --- presser/init.lua | 204 +++++------------------------------------------ 1 file changed, 20 insertions(+), 184 deletions(-) diff --git a/presser/init.lua b/presser/init.lua index 41632fe..6c959fa 100644 --- a/presser/init.lua +++ b/presser/init.lua @@ -1,198 +1,34 @@ -local popup = require'plenary.popup' -local utils = require'presser.utils' -local gcm = require('presser.context_manager') +local builtins = require'presser.builtins' -local api = vim.api +local a = vim.api local presser = {} -- list of functions to be exported - --- :@Dev: close all Presser windows that may be open for all contexts -presser.close = function () - gcm.flush() -- invoke the context manager to flush the table -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 ( _ctx, label, opts ) +presser.setup = function ( 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 = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, - } + -- @Dev: TODO - construct setup function for configs + -- - if opts.window then - for k,v in pairs( opts.window ) do - buf_opts[k] = v - end - end + --map("n", "h", "lua require'presser'.find_replace()") -- global replace with last buffer item - 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 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 } - ) + a.nvim_create_user_command( + 'Presser', + function ( o ) + local args = {} + for arg in string.gmatch( o.args, "%S+" ) do + table.insert( args, arg ) end - end - end -end - - -presser.move_next_buffer = function () - local ctxs = 0 - local c = vim.g.presser_buf_ctx - local buf = api.nvim_get_current_buf() - - for _, v in pairs( c ) do for idx, _ in pairs( v ) do ctxs = idx end end - - for ctx, ctx_obj in pairs( c ) do - for idx, obj in pairs( ctx_obj ) do - if api.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 - api.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, ctx_obj in pairs( c ) do - for idx, obj in pairs( ctx_obj ) do - if obj.allowed then - local buf = api.nvim_win_get_buf( obj.id ) - table.insert(b, utils.clean_buf( api.nvim_buf_get_lines( buf, 0, -1, false )[1] )) - end - end - end - - return b -end - -presser.execute = function () - local result = read_buffer() - presser.close() - - local cmd = ":%s/" .. result[1] .. "/" .. result[2] .. "/g" - api.nvim_exec( cmd, false ) -end - - -local start_buffer = function ( label ) - for ctx, ctx_obj in pairs( vim.g.presser_buf_ctx ) do - for idx, record in pairs( ctx_obj ) do - if record.what == label then - api.nvim_set_current_win( record.id ) - 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" - gcm.create( ctx ) - - local keymap = { - n = { - [""] = "lua require'presser'.close()", - [""] = "lua require'presser'.execute()", - }, - i = { - [""] = "lua require'presser'.move_next_buffer()", - [""] = "lua require'presser'.execute()", + builtins[args[1]]() + end, + { + nargs = 1, + complete = function (ArgLead, CmdLine, CursorPos) + return { 'find_replace' } + end, } - } + ) - -- get the centre of the current buffer - local c_cols = math.floor( api.nvim_win_get_width(0) / 2 ) - local c_lines = math.floor( api.nvim_win_get_height(0) / 2 ) --- print(c_cols, c_lines) - - -- options for the title buffer of the built-in - local opts = { - placeholder = "Find & Replace", - allowed = false, - window = { - line = c_lines - 3, - border = false, - minwidth = 82, - padding = { 0, 1, 1, 1 } - }, - } - local tab_title = new( ctx, "presser_fr_title", opts ) - - -- options for the replace buffer - local opts = { - window = { - line = c_lines, - title = "Find", - }, - keybinds = keymap, - } - local find = new ( ctx, "find_buf", opts ) - - -- modify options for find buffer - opts["window"]["line"] = c_lines+3 - opts["window"]["title"] = "Replace" - local replace = new( ctx, "replace_buf", opts ) - - local result = start_buffer( "find_buf" ) - api.nvim_feedkeys('A', 'n', false) end ---presser.find_replace() - return presser From b22ec2e733b020052aa963371c9810c321df2f92 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:13:21 +0000 Subject: [PATCH 09/14] 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. --- presser/actions/init.lua | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 presser/actions/init.lua diff --git a/presser/actions/init.lua b/presser/actions/init.lua new file mode 100644 index 0000000..f2e09db --- /dev/null +++ b/presser/actions/init.lua @@ -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 From 9fdc02deb4c3521d16c9f1fa6228333f98023eb2 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:18:06 +0000 Subject: [PATCH 10/14] 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. --- presser/builtins/__modules.lua | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 presser/builtins/__modules.lua diff --git a/presser/builtins/__modules.lua b/presser/builtins/__modules.lua new file mode 100644 index 0000000..b02fa9e --- /dev/null +++ b/presser/builtins/__modules.lua @@ -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 = { + [""] = "lua require'presser.actions'.close()", + [""] = "lua require'presser.actions'.execute()", + }, + i = { + [""] = "lua require'presser.actions'.move_next_buffer()", + [""] = "lua require'presser.actions'.execute()", + } + } + + -- 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 From 139e87b8e1ed2cbc994b196607ed2de989d71a60 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:25:56 +0000 Subject: [PATCH 11/14] 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). --- presser/builtins/init.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 presser/builtins/init.lua diff --git a/presser/builtins/init.lua b/presser/builtins/init.lua new file mode 100644 index 0000000..6726383 --- /dev/null +++ b/presser/builtins/init.lua @@ -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 From 657d3f2ae6efe3cf110d53170232978bc13210bc Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:29:21 +0000 Subject: [PATCH 12/14] 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. --- presser/steamers.lua | 78 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 presser/steamers.lua diff --git a/presser/steamers.lua b/presser/steamers.lua new file mode 100644 index 0000000..e35a93e --- /dev/null +++ b/presser/steamers.lua @@ -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 From 0d458544e0a2bd45c2fa0b75be1cf879dcfd1d69 Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:40:43 +0000 Subject: [PATCH 13/14] 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. --- {presser => lua/presser}/actions/init.lua | 0 {presser => lua/presser}/builtins/__modules.lua | 0 {presser => lua/presser}/builtins/init.lua | 0 {presser => lua/presser}/context_manager/init.lua | 0 {presser => lua/presser}/init.lua | 0 lua/presser/mappings/init.lua | 0 {presser => lua/presser}/steamers.lua | 0 {presser => lua/presser}/utils.lua | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {presser => lua/presser}/actions/init.lua (100%) rename {presser => lua/presser}/builtins/__modules.lua (100%) rename {presser => lua/presser}/builtins/init.lua (100%) rename {presser => lua/presser}/context_manager/init.lua (100%) rename {presser => lua/presser}/init.lua (100%) create mode 100644 lua/presser/mappings/init.lua rename {presser => lua/presser}/steamers.lua (100%) rename {presser => lua/presser}/utils.lua (100%) diff --git a/presser/actions/init.lua b/lua/presser/actions/init.lua similarity index 100% rename from presser/actions/init.lua rename to lua/presser/actions/init.lua diff --git a/presser/builtins/__modules.lua b/lua/presser/builtins/__modules.lua similarity index 100% rename from presser/builtins/__modules.lua rename to lua/presser/builtins/__modules.lua diff --git a/presser/builtins/init.lua b/lua/presser/builtins/init.lua similarity index 100% rename from presser/builtins/init.lua rename to lua/presser/builtins/init.lua diff --git a/presser/context_manager/init.lua b/lua/presser/context_manager/init.lua similarity index 100% rename from presser/context_manager/init.lua rename to lua/presser/context_manager/init.lua diff --git a/presser/init.lua b/lua/presser/init.lua similarity index 100% rename from presser/init.lua rename to lua/presser/init.lua diff --git a/lua/presser/mappings/init.lua b/lua/presser/mappings/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/presser/steamers.lua b/lua/presser/steamers.lua similarity index 100% rename from presser/steamers.lua rename to lua/presser/steamers.lua diff --git a/presser/utils.lua b/lua/presser/utils.lua similarity index 100% rename from presser/utils.lua rename to lua/presser/utils.lua From f841a23dded03d92a94716613c0e6714366ebeea Mon Sep 17 00:00:00 2001 From: TheOnePath Date: Fri, 13 Jan 2023 16:48:06 +0000 Subject: [PATCH 14/14] Updated context_manager/init.lua --- lua/presser/context_manager/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/presser/context_manager/init.lua b/lua/presser/context_manager/init.lua index 634a9b5..d49847e 100644 --- a/lua/presser/context_manager/init.lua +++ b/lua/presser/context_manager/init.lua @@ -105,7 +105,7 @@ end M.flush = function () for _, ctx_tbl in pairs( g.presser_buf_ctx ) do for _, data in pairs( ctx_tbl ) do - print("Deleting:", _, data.id) + --print("Deleting:", _, data.id) a.nvim_win_close( data.id, true ) -- buffer contents are irrelevant in this context to save end end