Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Open
TheLeoP wants to merge1 commit intoneovim:master
base:master
Choose a base branch
Loading
fromTheLeoP:vim.range

Conversation

@TheLeoP
Copy link
Contributor

As I mentioned in#25509 (comment) , I'm testing the currentvim.Pos andvim.Range implementation to find paint-points (ThePrimeagen/refactoring.nvim@fabfe7b is the commit where I refactored the code inrefactoring.nvim to 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 novim.Pos norvim.Range interface for:

  • mark-line indexing
  • treesitter indexing
  • api indexing
  • vimscript indexing

Solution:

Add said interfaces. This PR still leaves outto_mark andto_vimscript.


Problem:

There is no builtin way to tell if avim.Pos is inside of avim.Range.

Solution:

Addvim.Range.has_pos.


Problem:

Not allto_xxx interfaces have wrapping objects liketo_lsp

Solution:

Return unwrapped values inallto_xxx interfaces in theto_xxx interfaces where it makes sense. Accept unwrapped values in "from" interfaces where it makes sense, this also has the benefit of creatingvim.Ranges values like

localbuf=vim.api.nvim_get_current_buf()localnode=vim.treesitter.get_node()localrange=vim.range.treesitter(buf,node:range())

Problem:

start andend positions have different semantics, so they can't be compared.vim.Range relies on comparing theend andstart of two ranges to decide which one is greater, which doesn't work as expected because of the different semantics.

For example, for the ranges

locala= {start= {row=0,col=22,  },end_= {row=0,col=24,  },}localb= {start= {row=0,col=17,  },end_= {row=0,col=22,  },}

in the following code

localfoo,bar="foo","bar"--               |---||-|--                 b  a

The rangeb is smaller than the rangea, but the current implementation comparesb._end (col = 22) anda.start (col = 22) and concludes that, sinceb.col is not smaller thana.col,b should be greater thana.

Solution:

  • Add atype field tovim.Pos to distinguishend andstart positions and use it on__lt,__le and__eq to normalizeend andstart positions before comparing them. With the same example above,b._end would now be considered to havecol = 21 which is smaller thana.startscol = 22.
    • This would also avoid having separatedvim.Pos.treesitter_start/vim.Pos.treesitter_end functions (also applies forvim.Pos.mark andvim.Pos.api) and simply checking whether the position is astart or anend inside of each function.
  • If this is deemed not desired, an alternative could be changing the semantics ofvim.Pos so that bothstart andend are inclusive.

Problem:

Currently,vim.Range represents a 0 based end-exclusive range, but the Neovim API uses ranges withstart ==end to represent certain operations (e.g. insert text in a position withnvim_buf_set_text).

Solution:

Special casevim.Range.api andvim.Range.to_api to avoid modifyingend (by not performing the usual translations betweenvim.Range andapi-indexing semantics) whenstart ==end to keep them in sync. For example

localbuf=vim.api.nvim_get_curent_buf()localrange=vim.range(0,0,0,0, {buf=buf})-- ...localsrow,scol,erow,ecol=range:to_api()vim.api.nvim_buf_set_text(buf,srow,scol,erow,ecol, {'this is inserted at the beggining of the buffer'})

@TheLeoP
Copy link
ContributorAuthor

@bfredl Since I don't know the inner workings of indexing, does anything sticks out to you as wrong in this PR?

@TheLeoP
Copy link
ContributorAuthor

I left a bunch ofTODO(TheLeoP) comments with doubts about implementation details, so any feedback on them is also welcomed

@justinmk
Copy link
Member

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?

Copy link
Contributor

@ofseedofseed left a 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:

  1. 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;
  2. Prioritize refactoring other Ranges to usevim.Range, rather than providing a conversion for every kind of Range.

justinmk, clason, and TheLeoP reacted with thumbs up emoji
localapi=vim.api
localvalidate=vim.validate

---@aliasvim.Pos.Type'start'|'end'
Copy link
Contributor

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.

Copy link
ContributorAuthor

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

---
--- When specified, it indicates the type of this position.
--- This field is required when performing position conversions and comparisons.
---@fieldtype?vim.Pos.Type
Copy link
Contributor

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.

TheLeoP reacted with thumbs up emoji

--- Converts |vim.Pos| to |api-indexing| position.
---@paramposvim.Pos
---@returninteger,integer
Copy link
Contributor

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.

justinmk reacted with thumbs up emoji
Copy link
ContributorAuthor

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?

---@paramoutervim.Range
---@paramposvim.Pos
---@returnboolean`true`if{outer}range fully contains{post}.
functionRange.has_pos(outer,pos)
Copy link
Contributor

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.

justinmk, clason, and TheLeoP reacted with thumbs up emoji
--- ```
---@paramrangevim.Range
---@returninteger,integer,integer,integer
functionRange.to_treesitter(range)
Copy link
Contributor

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.

justinmk and clason reacted with thumbs up emojijustinmk reacted with rocket emoji
Copy link
ContributorAuthor

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
Copy link
ContributorAuthor

TheLeoP commentedOct 31, 2025
edited
Loading

Now that I have a better understanding of the whole picture, I'm thinking of doing the following (starting from scratch):

  • implementing 1 and 2 fromLua: Position / Range abstraction (vim.pos.Pos) #25509 (comment) (don't nestvim.Pos tables inside ofvim.Range , use numeric indices for bothvim.Pos andvim.Range instead ofrow,start_row, etc and use__index to allow them to be accessed withrow,start_row, etc)
  • add conversion interfaces formark andvimscript with tests (I could add only one if that's preffered)
  • document the behavior of ranges with row_exclusive-col_0 (as opposed to row_inclusive-col_exclusive) end positions
  • overloadvim.Range.has to also work wheninner is avim.Pos

Does that sound good?@justinmk

I would left for a future PR the following:

  • Handling the comparison betweenstart andend positions (either invim.Pos or in `vim.Range)
  • Implementing 3 fromLua: Position / Range abstraction (vim.pos.Pos) #25509 (comment) (add adata field tovim.Range to be able to allow ranges to have arbitrary data within them)
  • Maybe exposeextmark/to_extmark asapi/to_api instead
  • Refactor other usages of custom ranges on Neovim core to usevim.Range
  • Discuss if themark interface should accept arange_type parameter for either char, line or block wise ranges (similar to:h getregion())

@TheLeoPTheLeoPforce-pushed thevim.range branch 5 times, most recently from74a8545 toad510d3CompareNovember 6, 2025 16:41
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@ofseedofseedAwaiting requested review from ofseed

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@TheLeoP@justinmk@ofseed

[8]ページ先頭

©2009-2025 Movatter.jp