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: add vim.fs.relpath #31790

Merged
merged 1 commit into from
Jan 13, 2025
Merged

Conversation

dundargoc
Copy link
Member

This is needed to replace the nvim-lspconfig function is_descendant that
some lspconfg configurations still use.

@dundargoc
Copy link
Member Author

Alternative to #31702

@github-actions github-actions bot added the filesystem file metadata/attributes, filenames, path manipulation label Dec 30, 2024
@dundargoc
Copy link
Member Author

dundargoc commented Dec 30, 2024

@neovim/core: there's been some discussion in matrix chat without clear resolution whether this should work for target paths outside of the base directory. In other words, whether vim.fs.relpath("/var/lib", "/var/apache") should return ../apache or nil. I think ../apache is technically more correct but that returning nil would make the implementation easier. Other than that I'm not entirely decided. Input welcome if anyone has ideas.

For reference:
golang, zig, java, D, R, dart and realpath tool: returns ../apache
python: returns an error by default, but has an optional flag that allows outside paths in which case it would return ../apache
haskell: does not allow this case (so I guess an error?)
rust: does not have relative path functionality

@dundargoc dundargoc added the needs:discussion issue needs attention from an expert, or PR proposes significant changes to architecture or API label Dec 30, 2024
@echasnovski
Copy link
Member

I think ../apache is technically more correct but that returning nil would make the implementation easier.

Returning '../apache' definitely makes relpath() more versatile. My personal opinion is that it comes at somewhat high cost of (significantly) improved complexity of the function itself and the inability to use it to decide whether a path is a descendant of the base (thus requiring a separate function for that). Plus I don't think I can get a good enough practical use case for supporting returning '../apache' like output.

If the requirement is dropped, then implementation is fairly straightforward and allows vim.fs.relpath(path, dir) == nil be used to check whether path is descendant of dir:

vim.fs.relpath = function(from, to)
  from, to = vim.fs.normalize(vim.fs.abspath(from)), vim.fs.normalize(vim.fs.abspath(to))
  to = to .. (to ~= '/' and '/' or '')
  return vim.startswith(from, to) and from:sub(#to + 1) or nil
end

For reference, the relative path function for golang, zig, java, D and dart would return ../apache.

Resorting for what is commonly used in other languages might indeed be the way to go here. You can also add one of R's popular file system library to the list of "... would return ../apache".

@dundargoc

This comment was marked as resolved.

@gpanders
Copy link
Member

The realpath tool also returns ../apache:

$ realpath --relative-to /var/lib /var/apache
../apache

@justinmk
Copy link
Member

If the requirement is dropped, then implementation is fairly straightforward and allows vim.fs.relpath(path, dir) == nil be used to check whether path is descendant of dir:

Let's do that way then. We can add support for the "outside of target directory" case as a optional flag, later, if anyone actually requests it. But the default behavior should allow us to avoid is_relative_to.

@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch 2 times, most recently from d9f79ea to 39d4c95 Compare December 31, 2024 13:48
@dundargoc dundargoc removed the needs:discussion issue needs attention from an expert, or PR proposes significant changes to architecture or API label Dec 31, 2024
@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch 8 times, most recently from e816364 to 451b9b8 Compare January 2, 2025 13:13
@dundargoc dundargoc marked this pull request as ready for review January 2, 2025 13:17
@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch 2 times, most recently from 898c493 to 8f11cb7 Compare January 7, 2025 16:33
@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch 3 times, most recently from 1f7afe1 to 5ca0bf4 Compare January 7, 2025 20:43
@@ -741,4 +741,35 @@ function M.abspath(path)
return M.joinpath(cwd, path)
end

--- Gets `target` path relative to `base`, or `nil` if `base` is not an ancestor.
Copy link
Member

@wookayin wookayin Jan 7, 2025

Choose a reason for hiding this comment

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

or nil if base is not an ancestor.

What's the main motivation behind this design? Wouldn't base="/var/lib", target="/var/foo" => relpath = "../foo" also make sense (relpath in other programming languages implements such a behavior)?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry for a duplicate comment, I missed the previous discussion. But I'm still worried about potential breaking changes in the future and the design of the API.

Copy link
Member

@justinmk justinmk Jan 7, 2025

Choose a reason for hiding this comment

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

See #31790 (comment)

The current behavior allows relpath() to also provide "is relative to" functionality.

Wouldn't base="/var/lib", target="/var/foo" => relpath = "../foo" also make sense

We can get that in the future if it's wanted, by adding a opts flag. I think it's uncommon. It also complicates the logic. So if people want it, we can add it later.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with we can avoid a complication for uncommon scenario, but as a low-level stdlib I believe the correct and reasonable default behavior is to make it behave the same and consistentyl as the common relpath and other programming languages (including python's os.path.relpath).

I believe eventually we will implement the remaining case ("walk_up") sometime in the future, or at least some people will find the behavior of API surprising (arguably). If the function would work correctly only under a certain opt, that would sound weird in my humble opinion.

If the main purpose of adding this low-level vim.fs API is to implement is_descendant (with being performant), why don't we add a dedicated API instead?

So please consider requiring an explicit option such as opt = {walk_up = False} to achieve the (current) behavior, where the default option for the API ({ walk_up = true }) could remain yet-to-be implemented for the time being.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we could do that, although adding an option for an unimplemented feature sounds a bit odd. I'm fine with either option, I guess you'd need to convince @justinmk and @echasnovski though.

Copy link
Member

Choose a reason for hiding this comment

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

I believe the correct and reasonable default behavior is to make it behave the same and consistentyl as the common relpath and other programming languages (including python's os.path.relpath).

I didn't think about that. Though I can see that it would be useful for sibling files. But I have never needed that...

The common use-case for wanting a relative path is for a target that you know is descendant, and you want to avoid an absolute path (e.g. "workspace roots" for LSP or other project-like resolution).

Do you have some real-world examples where a "sibling" relative path is wanted?

Copy link
Member Author

Choose a reason for hiding this comment

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

@wookayin I'd prefer not to delay with this for much longer. If you have any examples in mind let us know, otherwise I'll go ahead and merge this soon.

Copy link
Member

Choose a reason for hiding this comment

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

If you have any examples in mind let us know, otherwise I'll go ahead and merge this soon.

Might be worth adding a third opts argument for future use and explicitly say that it is "reserved for future use". Not mandatory, since adding it later seems to follow the API contract for Lua stdlib.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, add an opts argument

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll merge this, we can change the signature before release if it turns out doing the other way is more useful.

@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch 3 times, most recently from 3bde16d to ffc4fd7 Compare January 11, 2025 17:21
@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch from c4d16e5 to 19325be Compare January 11, 2025 20:05
This is needed to replace the nvim-lspconfig function is_descendant that
some lspconfg configurations still use.
@dundargoc dundargoc force-pushed the feat/vim.fs.relpath branch from 19325be to d3a7893 Compare January 11, 2025 22:06
@dundargoc dundargoc requested a review from wookayin January 12, 2025 00:31
@dundargoc dundargoc merged commit 0631492 into neovim:master Jan 13, 2025
31 checks passed
@dundargoc dundargoc deleted the feat/vim.fs.relpath branch January 13, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
filesystem file metadata/attributes, filenames, path manipulation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants