Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork6.4k
feat: extend vim.Pos and vim.Range#36397
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:master
Are you sure you want to change the base?
Conversation
TheLeoP commentedOct 30, 2025
@bfredl Since I don't know the inner workings of indexing, does anything sticks out to you as wrong in this PR? |
TheLeoP commentedOct 30, 2025
I left a bunch of |
justinmk commentedOct 30, 2025
Very nice! Can we start with the simplest case (api, or treesitter, or ...) and focus on merging that before moving to the more difficult cases? |
ofseed left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I didn't add all the type conversion functions at once before because I considered the following:
- Add them when they are actually needed, so we can clearly know what kind of return value we require, and this helps ensure higher test coverage;
- Prioritize refactoring other Ranges to use
vim.Range, rather than providing a conversion for every kind of Range.
runtime/lua/vim/pos.lua Outdated
| localapi=vim.api | ||
| localvalidate=vim.validate | ||
| ---@aliasvim.Pos.Type'start'|'end' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Is there any need to distinguishstart orend onPos? I think the related operations would be better onRange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
You need to distinguishstart fromend to be able to correctly compare (i.e.<,>, etc) two positions
runtime/lua/vim/pos.lua Outdated
| --- | ||
| --- When specified, it indicates the type of this position. | ||
| --- This field is required when performing position conversions and comparisons. | ||
| ---@fieldtype?vim.Pos.Type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Even if such a field should be added, I don't thinktype is a good name because it's too general. Since one of our main goals is to do "type conversions" of positions, that name would be confusing. Something more specific likebound would be better.
runtime/lua/vim/pos.lua Outdated
| --- Converts |vim.Pos| to |api-indexing| position. | ||
| ---@paramposvim.Pos | ||
| ---@returninteger,integer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can't this be achieved withto_extmark? IIRC,:h api-indexing only distinguishes between two types, one forextmark and one formark.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I read:h api-indexing as "there are 3 types of API indexing: regular indexing (0-based, end-exclusive up to n byte), mark indexing (1-based, not mention about end-type), extmark indexing (0-based, end-inclusive up to n + 1 byte )". But, with testing, I'm seeing that this is more of a conceptual distinction, both regular indexing and extkmark indexing seem to go up to n bytes, but that's considered exclusive for functions likenvim_buf_get_text and inclusive for functions likenvim_buf_get_extmarks. I can group both together inside ofvim.Pos andvim.Range, is there a preference between using eitherextmark orapi for both cases?
runtime/lua/vim/range.lua Outdated
| ---@paramoutervim.Range | ||
| ---@paramposvim.Pos | ||
| ---@returnboolean`true`if{outer}range fully contains{post}. | ||
| functionRange.has_pos(outer,pos) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think we need to provide a separatehas_pos; instead, overload the existinghas to accept aPos as a parameter.
runtime/lua/vim/range.lua Outdated
| --- ``` | ||
| ---@paramrangevim.Range | ||
| ---@returninteger,integer,integer,integer | ||
| functionRange.to_treesitter(range) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Range is something I implemented based on the existingvim.treesitter.Range. My original idea was to usevim.Range to replace all the range types we use internally. Therefore, where possible, I think it would be better forvim.treesitter to usevim.Range, thereby avoiding the introduction of additional types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Got it.
Initially, my understanding was thatvim.Range was meant to always normalize end positions at the end of a line (no matter if they included the newline or not) to be row_exclusive-col_0, but I'm realizing that there's nothing in the original implementation that suggest that. I'll simply document explicitly that row_exclusive-col_0 end positions in ranges include newlines.
TheLeoP commentedOct 31, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Now that I have a better understanding of the whole picture, I'm thinking of doing the following (starting from scratch):
Does that sound good?@justinmk I would left for a future PR the following:
|
74a8545 toad510d3Compare
As I mentioned in#25509 (comment) , I'm testing the current
vim.Posandvim.Rangeimplementation to find paint-points (ThePrimeagen/refactoring.nvim@fabfe7b is the commit where I refactored the code inrefactoring.nvimto use my bundled version of them). This PR includes all the problems I've found and my proposed solutions for them. Let me know if it would be better to split it into smaller PRs.I also haven't included tests yet because I'm expecting discussion about how to better solve the issues I've found.
Problem:
There is no
vim.Posnorvim.Rangeinterface for:Solution:
Add said interfaces. This PR still leaves out
to_markandto_vimscript.Problem:
There is no builtin way to tell if a
vim.Posis inside of avim.Range.Solution:
Add
vim.Range.has_pos.Problem:
Not all
to_xxxinterfaces have wrapping objects liketo_lspSolution:
Return unwrapped values in
allin theto_xxxinterfacesto_xxxinterfaces where it makes sense. Accept unwrapped values in "from" interfaces where it makes sense, this also has the benefit of creatingvim.Rangesvalues likeProblem:
startandendpositions have different semantics, so they can't be compared.vim.Rangerelies on comparing theendandstartof two ranges to decide which one is greater, which doesn't work as expected because of the different semantics.For example, for the ranges
in the following code
The range
bis smaller than the rangea, but the current implementation comparesb._end(col = 22) anda.start(col = 22) and concludes that, sinceb.colis not smaller thana.col,bshould be greater thana.Solution:
typefield tovim.Posto distinguishendandstartpositions and use it on__lt,__leand__eqto normalizeendandstartpositions before comparing them. With the same example above,b._endwould now be considered to havecol = 21which is smaller thana.startscol = 22.vim.Pos.treesitter_start/vim.Pos.treesitter_endfunctions (also applies forvim.Pos.markandvim.Pos.api) and simply checking whether the position is astartor anendinside of each function.vim.Posso that bothstartandendare inclusive.Problem:
Currently,
vim.Rangerepresents a 0 based end-exclusive range, but the Neovim API uses ranges withstart==endto represent certain operations (e.g. insert text in a position withnvim_buf_set_text).Solution:
Special case
vim.Range.apiandvim.Range.to_apito avoid modifyingend(by not performing the usual translations betweenvim.Rangeandapi-indexingsemantics) whenstart==endto keep them in sync. For example