Skip to content

Commit

Permalink
feat(core): support dot repeat
Browse files Browse the repository at this point in the history
  • Loading branch information
Goose97 committed Nov 21, 2024
1 parent ca6fb5f commit cfb9817
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 69 deletions.
125 changes: 85 additions & 40 deletions lua/neolog/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ local highlight = require("neolog.highlight")
local treesitter = require("neolog.actions.treesitter")
local utils = require("neolog.utils")

---@class NeologActionsState
---@field current_command_arguments {insert_log: InsertLogOptions?, insert_batch_log: InsertBatchLogOptions?}
---@field current_selection_range {[1]: number, [2]: number, [3]: number, [4]: number}?
---@field last_command_moved_cursor boolean Whether the last command moved the cursor position

---@type NeologActionsState
local state = {
current_command_arguments = {},
current_selection_range = nil,
last_command_moved_cursor = false,
}

---@param callback string
local function make_dot_repeatable(callback)
-- Reset the operatorfunc
vim.go.operatorfunc = "v:lua.require'neolog.utils'.NOOP"
vim.cmd("normal! g@l")
vim.go.operatorfunc = "v:lua.require'neolog.actions'." .. callback
end

---@param line_number number 1-indexed
local function indent_line_number(line_number)
local current_pos = vim.api.nvim_win_get_cursor(0)
Expand Down Expand Up @@ -104,8 +124,14 @@ local function after_insert_log_statements(statements)
-- 2. Move to the first character
-- 3. Move left by the offset
-- 4. Go to insert mode
vim.cmd(string.format("normal! %dG^%dl", row + 1, offset - 1))
vim.cmd("startinsert")
-- We need to defer because the function is executed in normal mode by g@ operator
-- After the function is executed, we can go to insert mode
vim.defer_fn(function()
vim.cmd(string.format("normal! %dG^%dl", row + 1, offset - 1))
vim.cmd("startinsert")
end, 0)

state.last_command_moved_cursor = true
end
end

Expand All @@ -126,11 +152,7 @@ end
---@param log_targets TSNode[]
---@return TSNode[][]
local function group_overlapping_log_targets(log_targets)
-- Add index to make sure the sort is stable
log_targets = utils.array_sort_with_index(log_targets, function(a, b)
local result = utils.compare_ts_node_start(a[1], b[1])
return result == "equal" and a[2] < b[2] or result == "before"
end)
log_targets = treesitter.sort_ts_nodes_preorder(log_targets)

local groups = {}

Expand Down Expand Up @@ -179,17 +201,7 @@ local function pick_best_node(nodes, selection_range)
return nodes[1]
end

-- Sort by node start then by node end (descending)
-- Add index to make sure the sort is stable
nodes = utils.array_sort_with_index(nodes, function(a, b)
local result = utils.compare_ts_node_start(a[1], b[1])
if result == "equal" then
result = utils.compare_ts_node_end(a[1], b[1])
return result == "equal" and a[2] < b[2] or result == "after"
else
return result == "before"
end
end)
nodes = treesitter.sort_ts_nodes_preorder(nodes)

-- @type TSNode?
local best_node = utils.array_find(nodes, function(node)
Expand All @@ -202,7 +214,8 @@ end
---@param lang string
---@return {log_container: TSNode, logable_range: logable_range?, log_targets: TSNode[]}[]
local function capture_log_targets(lang)
local selection_range = utils.get_selection_range()
-- If state.current_selection_range is nil, this means the user is dot repeating.
local selection_range = state.current_selection_range or utils.get_selection_range()
local log_containers = treesitter.query_log_target_container(lang, selection_range)

local result = {}
Expand Down Expand Up @@ -391,12 +404,9 @@ end

--- @alias LogPosition "above" | "below"

--- Insert log statement for the current identifier at the cursor
--- @class InsertLogOptions
--- @field template string? Which template to use. Defaults to `default`
--- @field position LogPosition
--- @param opts InsertLogOptions
function M.insert_log(opts)
function M.__insert_log(_)
local opts = state.current_command_arguments.insert_log

opts = vim.tbl_deep_extend("force", { template = "default" }, opts or {})
local log_template_lang, lang = get_lang_log_template(opts.template, "single")

Expand All @@ -415,13 +425,32 @@ function M.insert_log(opts)

insert_log_statements(to_insert)
after_insert_log_statements(to_insert)

make_dot_repeatable("__insert_log")
end

--- Insert log statement for given batch
--- @class InsertBatchLogOptions
--- Insert log statement for the current identifier at the cursor
--- @class InsertLogOptions
--- @field template string? Which template to use. Defaults to `default`
--- @param opts InsertBatchLogOptions?
function M.insert_batch_log(opts)
--- @field position LogPosition
--- @param opts InsertLogOptions
function M.insert_log(opts)
state.current_command_arguments.insert_log = opts
state.current_selection_range = utils.get_selection_range()

local cursor_position = vim.fn.getpos(".")
vim.go.operatorfunc = "v:lua.require'neolog.actions'.__insert_log"
vim.cmd("normal! g@l")
-- If log template use %insert_cursor, don't restore the cursor position
if not state.last_command_moved_cursor then
vim.fn.setpos(".", cursor_position)
end

state.current_selection_range = nil
end

function M.__insert_batch_log(_)
local opts = state.current_command_arguments.insert_batch_log
opts = vim.tbl_deep_extend("force", { template = "default" }, opts or {})

if #M.batch == 0 then
Expand All @@ -438,12 +467,22 @@ function M.insert_batch_log(opts)
insert_log_statements({ to_insert })
after_insert_log_statements({ to_insert })
M.clear_batch()

make_dot_repeatable("__insert_batch_log")
end

---Add log target to the log batch
function M.add_log_targets_to_batch()
local mode = vim.api.nvim_get_mode().mode
--- Insert log statement for given batch
--- @class InsertBatchLogOptions
--- @field template string? Which template to use. Defaults to `default`
--- @param opts InsertBatchLogOptions?
function M.insert_batch_log(opts)
vim.go.operatorfunc = "v:lua.require'neolog.actions'.__insert_batch_log"
state.current_command_arguments.insert_batch_log = opts
vim.cmd("normal! g@l")
end

---Add log target to the log batch
function M.__add_log_targets_to_batch()
local lang = get_lang(vim.bo.filetype)
if not lang then
vim.notify("Cannot determine language for current buffer", vim.log.levels.ERROR)
Expand All @@ -459,20 +498,26 @@ function M.add_log_targets_to_batch()
end
end

to_add = utils.array_sort_with_index(to_add, function(a, b)
local result = utils.compare_ts_node_start(a[1], b[1])
return result == "equal" and a[2] < b[2] or result == "before"
end)
to_add = treesitter.sort_ts_nodes_preorder(to_add)

vim.list_extend(M.batch, to_add)

if mode == "v" or mode == "V" then
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
end

for _, target in ipairs(to_add) do
highlight.highlight_add_to_batch(target)
end

make_dot_repeatable("add_log_targets_to_batch")
end

function M.add_log_targets_to_batch()
state.current_selection_range = utils.get_selection_range()

local cursor_position = vim.fn.getpos(".")
vim.go.operatorfunc = "v:lua.require'neolog.actions'.__add_log_targets_to_batch"
vim.cmd("normal! g@l")
vim.fn.setpos(".", cursor_position)

state.current_selection_range = nil
end

function M.get_batch_size()
Expand Down
6 changes: 4 additions & 2 deletions lua/neolog/actions/treesitter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ local M = {}
local utils = require("neolog.utils")

---Sort the given nodes in the order that they would appear in a preorder traversal
local function sort_ts_nodes_preorder(nodes)
---@param nodes TSNode[]
---@return TSNode[]
function M.sort_ts_nodes_preorder(nodes)
return utils.array_sort_with_index(nodes, function(a, b)
local result = utils.compare_ts_node_start(a[1], b[1])
if result == "equal" then
Expand Down Expand Up @@ -100,7 +102,7 @@ function M.find_log_targets(containers, lang)

-- If there's multiple containers for the same log target, pick the deepest container
for log_target_id, log_containers in pairs(grouped_log_targets) do
local sorted_group = sort_ts_nodes_preorder(log_containers)
local sorted_group = M.sort_ts_nodes_preorder(log_containers)
local deepest_container = sorted_group[#sorted_group]

local log_target = log_targets_table[log_target_id]
Expand Down
5 changes: 3 additions & 2 deletions lua/neolog/utils.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
local M = {}

-- Do nothing.
M.NOOP = function() end

function M.array_find(array, predicate)
for i, v in ipairs(array) do
if predicate(v, i) then
Expand Down Expand Up @@ -175,8 +178,6 @@ end
---@return {[1]: number, [2]: number, [3]: number, [4]: number}
function M.get_selection_range()
local mode = vim.api.nvim_get_mode().mode
-- After exiting visual mode, these marks will be available
-- (1,1)-indexed position
local result1 = vim.fn.getpos("v")
local result2 = vim.fn.getpos(".")
local srow = result1[2]
Expand Down
5 changes: 2 additions & 3 deletions tests/neolog/actions/lang/jsx_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@ describe("javascriptreact", function()
}
]]

-- TODO: figure out why indentation is off with inner jsx element
expected = [[
function foo() {
const a = 1
const b = true
const el = (
<div>
{b && <div>{a + 1}</div>}
<input className={c} />
{b && <div>{a + 1}</div>}
<input className={c} />
</div>
)
console.log("b", b)
Expand Down
6 changes: 3 additions & 3 deletions tests/neolog/actions/lang/lua_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ describe("lua", function()
local function foo(
bar,
baz,
)
print("bar", bar)
print("baz", baz)
)
print("bar", bar)
print("baz", baz)
return nil
end
]],
Expand Down
5 changes: 2 additions & 3 deletions tests/neolog/actions/lang/tsx_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@ describe("typescriptreact", function()
}
]]

-- TODO: figure out why indentation is off with inner jsx element
expected = [[
function foo() {
const a = 1
const b = true
const el = (
<div>
{b && <div>{a + 1}</div>}
<input className={c} />
{b && <div>{a + 1}</div>}
<input className={c} />
</div>
)
console.log("b", b)
Expand Down
Loading

0 comments on commit cfb9817

Please sign in to comment.