Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(functions): Mode aware functions #415

Closed
wants to merge 6 commits into from

Conversation

hinell
Copy link
Contributor

@hinell hinell commented Oct 19, 2023

functions-demo-switched-mode.mov

Summary:

  • feat(functions): populate mode_mappings
  • feat(functions): add methods for UI display filtering
  • feat(functions): allow filtering by functions
  • feat(functions): display multi-mode functions
  • docs(functions): describe new mode=field

Note

I request to squash-rebase

How to Test

New funcs = { ... mode = 'n' } field for functions - its type should be either string or table<string>; that's it

Testing for Regressions

I have tested the following:

  • Triggering keymaps from legendary.nvim in all modes (normal, insert, visual)
  • Creating keymaps via legendary.nvim, then triggering via the keymap in all modes (normal, insert, visual)
  • Triggering commands from legendary.nvim in all modes (normal, insert, visual)
  • Creating commands via legendary.nvim, then running the command manually from the command line
  • augroup/autocmds created through legendary.nvim work correctly

## Specifying mode

By default, the funciton is shown and run in `*` (all) modes.
You can use `mode` property to narrow function's scope, so it always run in specified mode:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can use `mode` property to narrow function's scope, so it always run in specified mode:
You can use `mode` property to narrow function's scope, so it only appears in the specified mode(s):

mode = {
tbl.mode,
is_list_of_strings_or_string,
'item.mode should contain only strings of modes: n, i, v etc.',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'item.mode should contain only strings of modes: n, i, v etc.',
'item.mode must be a string or list of strings specifying modes: n, i, v etc.',

Comment on lines +62 to +67
local modeCurrent = vim.fn.mode()
for _, modeInstance in ipairs(instance.mode_mappings) do
if modeCurrent == modeInstance then
impl()
end
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local modeCurrent = vim.fn.mode()
for _, modeInstance in ipairs(instance.mode_mappings) do
if modeCurrent == modeInstance then
impl()
end
end
local current_mode = vim.fn.mode()
if not vim.tbl_contains(instance.mode_mappings, current_mode) then
return
end
impl()

Comment on lines +70 to +72
-- mode_mapping is going to remain static during legendary runtime
-- so we can cache current mapping state
instance._mode_switched = vim.tbl_islist(instance.mode_mappings) and not vim.tbl_isempty(instance.mode_mappings)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel necessary to me to cache this.

Suggested change
-- mode_mapping is going to remain static during legendary runtime
-- so we can cache current mapping state
instance._mode_switched = vim.tbl_islist(instance.mode_mappings) and not vim.tbl_isempty(instance.mode_mappings)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used for displaying: 29513d1#r1366849251

; It's essential for both performance reason and readability

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't think it will have a noticeable impact on performance, and I don't see how it affects readability either, personally.

If you can show some benchmarks proving otherwise I'm all ears.

Copy link
Contributor Author

@hinell hinell Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrjones2014 It's cheap to check once and leave, then checking whether the list is empty all the time when the list doesn't change. This is obvious.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s an extremely inexpensive check. It doesn’t need to be cached.

Comment on lines +90 to +94
--- Intended to be used by UI filters
function Function:modeSwitched()
return self._mode_switched
end

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the diff it looks like this function isn't needed either.

Suggested change
--- Intended to be used by UI filters
function Function:modeSwitched()
return self._mode_switched
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used for displaying: 29513d1#r1366849251

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you don't have to do it that way though -- see the other comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to refactor it after a merge

Comment on lines +96 to +102
if self:modeSwitched() then
return self.mode_mappings
else
-- Just use all modes for UI filtering
-- it's half-assed because UI filtering is ugly ¯\_(ツ)_/¯
return { 'n', 'V', 'v', 'x', 's', 'o', '' }
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep it consistent with how the mode_mappings API works for other items.

Suggested change
if self:modeSwitched() then
return self.mode_mappings
else
-- Just use all modes for UI filtering
-- it's half-assed because UI filtering is ugly ¯\_(ツ)_/¯
return { 'n', 'V', 'v', 'x', 's', 'o', '' }
end
if vim.tbl_islist(self.mode_mappings) then
return self.mode_mappings
end
local modes = {}
for mode, mapping in pairs(self.mode_mappings) do
if mapping then
table.insert(modes, mode)
end
end
if #modes == 0 then
return { 'n', 'V', 'v', 'x', 's', 'o', '' }
end
return modes

Copy link
Contributor Author

@hinell hinell Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require a lot of changes in Function's constructor where mode_mappings is built . See my reply below.

let's keep it consistent

Well, mode-map is not consistent across keymaps, commands, autocommands, and functions.
This PR lays a groundwork for further extension; I personally see no reason for complicated mode-map. Again, see my reply below.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an intent of this PR; I'm fine if you refactor it further after merging it;

there is a lof of space for improvement in this project

@@ -38,11 +38,15 @@ function M.default_format(item)
item.description,
}
elseif Toolbox.is_function(item) then
-- stylua: ignore start
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylua makes lines in-between ugly otherwise

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather just have the formatter format it than stray from the style of everything else in the entire codebase.

'<function>',
item.description,
}
-- stylua: ignore end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be ignoring formatting unless there's a good reason.

Comment on lines +43 to +45
item--[[@as keymap ]]:modeSwitched()
and table.concat(item--[[@as Keymap]]:modes(), ', ')
or Config.icons.fn,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
item--[[@as keymap ]]:modeSwitched()
and table.concat(item--[[@as Keymap]]:modes(), ', ')
or Config.icons.fn,
table.concat(item--[[@as Function]]:modes() or { Config.icons.fn }, ', '),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, look carefully what you suggest; this introduces a bug; item:modeSwitched() is essential here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't introduce a bug if you also tweak the implementation of modes() as I suggested.

---@class Function
---@field implementation function
---@field mode_mappings string[]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have the mode_mappings be a different type for functions than for everything else.

Take a look at how Keymap handles it. The type should be something similar to ModeKeymap, but you'll just need to modify it slightly to take different options (the options for a Function).

Copy link
Contributor Author

@hinell hinell Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a lot for an overnight PR which I made without a prior knowledge of this project, isn't it?

I didn't intend to implement fully keymap-compatible mode-map, cuz:

  • Nobody is going to drop funcs items into legendary.keymap({ ... })
  • Even if they do, current modemap type is fully forward compatible with legendary.keymap({ ... });
  • Zero use cases
  • No need for complicated ways of mode-map declaration

Ambiguities

you need just slightly

Only to make things more complicated: validation (see below) requires that item's [1]-field is provided with a function, so introduction of keymaps-like node-map will likely break something - you have to figure you what to do with that function; it's subject for further discussion.

I suggest you take care of map enhancement further after merging this PR.

function Function:parse(tbl) -- luacheck: no unused
vim.validate({
['1'] = { tbl[1], { 'function' } },
description = { util.get_desc(tbl), { 'string' } },
opts = { tbl.opts, { 'table' }, true },
})

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a lot for an overnight PR which I made without a prior knowledge of the project, isn't it?

Not sure what you mean. There's no deadline on the PR.

Regarding your 3 bullets, addressing in-order:

  • That's not what I said and it's not what my comment is meant to support
  • See above
  • The use case is to support the alternate declaration syntax (2nd code block here)

Only to make things more complicated: validation (see below) requires that item's [1]-field is provided with a function, so introduction of keymaps-like node-map will likely break something - you have to figure you what to do with that function; it's subject of further discussion.

It's not really more complicated. You can copy the validator from keymap.lua only for the options, validate that they're Function options instead of Keymap options. Then just update executor.lua to pull the right implementation out of the item based on the mode.

It we're going to introduce mode_mappings for Functions, then they're going to work the same as they do for Keymaps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(2nd code block here)
then they're going to work the same as they do for Keymaps.

As I said, this PR is about functions, not keymaps; if you want to unify mode-map apis for both, I would suggest you to take care of further mode-map improvement yourself taking the laid groundwork as basis; for me, it's more than enough to have mode = { 'n', 'v', 'x' } per one function; for more - i would just create more items with different mode; cheap and quick

And as I said, I see no reason to introduce that complicated approach, cause there are going to be ambiguities you will have to resolve as project owner

@mrjones2014
Copy link
Owner

Also, please update the PR title per CONTRIBUTING.md

@hinell hinell changed the title Mode aware functions feat(functions): Mode aware functions Oct 20, 2023
@mrjones2014
Copy link
Owner

Since it seems like you're totally unwilling to work with me to address the issues in the PR, I'm just going to close it. Feel free to reopen or open a new PR if you decide you're willing to follow the project guidelines and code reviews from project maintainers.

@hinell
Copy link
Contributor Author

hinell commented Oct 20, 2023

If you want more features - go ahead, I'm fine;

My point is: I suggest the laid ground work for futher improvement, it's obviously beneficial to have half of the job done, then doing it yourself

It's also faster to merge and improve, than bogging down this discussion in very vague, or obviously incorrect suggestions

It's sad that I have to maintain a separate fork of this project; anyway

@mrjones2014
Copy link
Owner

it's obviously beneficial to have half of the job done

Sure, you're definitely right about that. That doesn't mean I'm going to merge the feature in its current half-baked state.

It's also faster to merge and improve

Yes it's faster, that doesn't mean I'm okay with merging half-baked features that don't align with existing features for other item types.

than bogging down this discussion in very vague, or obviously incorrect suggestions

Please explain to me how you, who by your own admission doesn't have any prior knowledge of the project, would be able to identify that better than the project maintainer, and point me to a suggestion I made that is "obviously incorrect".

It's sad that I have to maintain a separate fork of this project; anyway

I'm more than willing to work with you to get the PR into a mergeable state, however you don't seem willing to accept any of the feedback or address any of the problems with the PR.

If you'd rather maintain your own fork than actually collaborate, then go ahead, but open source collaboration doesn't mean merging incomplete PRs just because you say so.

Maybe you could try being a little less rude next time 🙂

Repository owner deleted a comment from hinell Oct 20, 2023
Repository owner deleted a comment from hinell Oct 20, 2023
Repository owner deleted a comment from hinell Oct 20, 2023
@mrjones2014
Copy link
Owner

Deleted a bunch of useless vile rudeness comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants