grapple.nvim
grapple.nvim copied to clipboard
proposal: "frozen" file tags
Frozen Tags
Context
I have seen this suggestion/idea pop up many times across many different plugins which are attempting to make file/buffer navigation easier. It's even been requested here (#58 #65 #66). This seems to be some way to bridge the gap between harpoon-like file marking and :h mark-motions
.
For those unfamiliar, Vim marks allow you to do two things:
- Use
'a
(lowercase) marks to mark a specific location in some file, globally - Use
'A
(uppercase) to mark a specific location in a specific file, globally
Grapple compliments the above system by allowing users to "tag" a file, but not a specific location. Rather, the cursor is tracked and will be restored when the tag is selected. In addition, tags are scoped per-project instead of globally.
There is a gap, however. What if you want that tag to stop updating, temporarily? What if you want to tag a specific location, but keep it local to the project you're working on?
Proposal
I would like to propose the following: frozen tags. These would be similar to regular tags, but their cursor position would never update. In addition to never updating, there would be more than one allowed per file.
Although it would be recommended that a frozen tag be created with a name, it would not be required. Selection behaviour would still always prefer the non-frozen tag, if not specified.
NOTE: default behaviour would remain the same. Non-frozen (dynamic) tags would still be restricted to one-per-filepath and track the last known cursor location.
For example, creating a frozen tag would look something like this:
require("grapple").tag({ buffer = 0, name = "a", frozen = true })
And to select a frozen tag:
require("grapple").select({ name = "a", frozen = true })
You could convert a non-frozen tag into a frozen tag, creating a new file tag if it doesn't exist (this would likely be a simple wrapper around Grapple.tag
):
require("grapple").freeze({ buffer = 0 })
You could convert a frozen tag into a non-frozen tag, overwriting (or not) an existing non-frozen tag if one does exist (again, this would likely be a simple wrapper around Grapple.tag
):
require("grapple").unfreeze({ buffer = 0, overwrite = true })
Implementation
The implementation is a bit fuzzy right now, but grapple.tag
would look a bit like this:
---@class grapple.tag
---@field path string absolute path
---@field name string | nil (optional) tag name
---@field cursor integer[] (1, 0)-indexed cursor position
---@field frozen boolean
And the grapple.tag_container
would need to accommodate more than one file per tag. That would require some refactor work, but not be too difficult.
The UI would need some experimentation to ensure it doesn't distract too much, add more overhead, or feel out-of-place.
To summarize this
- Allow the option to jump to a specific line + file
- (currently I believe the default is to go to the previous cursor position in the file). But I think grapple does allow you to save the current cursor position?
- Allow "naming" a position, similar to marks
- If naming is based on single letters, maybe text motions could be implemented for these frozen tags?
- Allow multiple tags per file
- The UI view of grapple would likely need an update, idk, maybe a tree view?
- some/file.txt
- [a] - line 10 - some frozen tag
- [b] - line 15 - another frozen tag
- [c] - line 21 - etc
- ...
- some/file.txt
Like that?
@ColinKennedy Thanks for taking the time to summarize. To clarify:
I think grapple does allow you to save the current cursor position?
Correct. A cursor position can be specified with cursor = { x, y }
, but will be tracked and overridden to the previous cursor position by default. With the proposed changes, frozen = true
will ensure a set cursor position is not tracked or overridden.
Allow "naming" a position, similar to marks
Correct. Grapple already allows names with (i.e. name = "a"
). However, to push this point a bit, I am leaning towards having named tags remain local to the project, not buffer. That means, for example:
- Allowed: frozen tag with name
a
on file A, frozen tag with nameb
on file A - Allowed: frozen tag with name
a
on file A, frozen tag with nameb
on file B -
Disallowed: frozen tag with name
a
on file A, frozen tag with namea
on file B
The UI view of grapple would likely need an update, idk, maybe a tree view?
A tree view would likely complicate things. It could likely remain a list view with a the cursor as an additional detail. For example (mockup),
A list view would work, though it would be nice if users can customize where each piece of info goes per-row. For example I would prefer to have it be mark | relative/project path | (row, column)
whereas others might want (row, column) | mark | relative/project path
as your mockup shows
While I agree that providing formatting options would be useful, I think that might be better suited as a later feature. For now, there are already a couple options available in the settings (see style
and name_pos
).
I find myself looking into this issue every couple of months because it'll be so nice when implemented!
I thought grapple.nvim already remembered cursor position (coming from reading this issue: https://github.com/cbochs/grapple.nvim/issues/72). Looking forward for this proposal! :+1:
Heh PERFECT timing. I was at it yesterday to hack about in the grapple code to get exactly this feature going. I haven't yet been successful, and your implementation sounds great, because I was just toying with the autocmds.
in #72 there is a workaround to remove the autocmds and to hodge podge some things, but it's not ideal, because if you remove the auto commands, you never get a cursor location saved since it's ONLY saved via autocmd for me, never when I first create the tag.