Skip to content

Conversation

@kkrime
Copy link

@kkrime kkrime commented Jan 7, 2025

No description provided.

Marc Jakobi and others added 2 commits January 2, 2025 07:53
Some LSP clients can have more than one workspace folder, which
can be added via
[`workspace/didChangeWorkspaceFolders`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWorkspaceFolders).

This is especially useful for resource-hungry clients, like
rust-analyzer or haskell-language-server, in order to avoid spawning
multiple clients.

Using `lsp.Client.config.root_dir` as the source of truth results
in this plugin detecting the wrong project root in this scenario.

This PR fixes that, by first searching through the
`lsp.Client.workspace_folder`s,
and then falling back to the `config.root_dir` if no matching
workspace folder is found.
describe('M._get_buf_root()):', function()
it("testing M._get_buf_root() returns correct buf_root for embedded projects", function()
local outter_project = "/Users/kkrime/outter_project"
local inner_project = "/Users/kkrime/outter_project/inner_project"
Copy link

Choose a reason for hiding this comment

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

unsure if it really matters, but it seems some more universal paths like /tmp/outer_project could work better

Copy link
Author

Choose a reason for hiding this comment

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

It really doesn't make any difference, you can change it you feel strongly about it, no problem with me

@kkrime kkrime force-pushed the corner_case_embedded_projects branch from 72c6c5b to c11869a Compare January 8, 2025 01:18
@magnusriga
Copy link

@kkrime Quick question: I tried this plugin, via LazyVim, but the list of recent projects remains empty. Is it broken and no longer maintained? Are your PRs being merged here or in another fork?

Thank you!

@kkrime
Copy link
Author

kkrime commented Mar 3, 2025

@magnusriga I'm not sure what's happening with this project tbh, it hasn't been touched by the maintainer in a while, it's looking like it's been abandoned.

I'm not sure what you mean by the list of recent projects remains empty ?

@magnusriga
Copy link

magnusriga commented Mar 3, 2025

@magnusriga I'm not sure what's happening with this project tbh, it hasn't been touched by the maintainer in a while, it's looking like it's been abandoned.

I'm not sure what you mean by the list of recent projects remains empty ?

@kkrime Thank you for the quick reply. I tried to run get_recent_projects(), but that table is just empty.

Does that table fill up on your end, when you open files and LSP clients attach?

@kkrime
Copy link
Author

kkrime commented Mar 3, 2025

@magnusriga Ya, it works for me, did you configure detection_methods and patterns?

@magnusriga
Copy link

magnusriga commented Mar 3, 2025

@kkrime I am using lazy.nvim and did:

  {
    "kkrime/project.nvim",
    opts = { },
    event = "VeryLazy",
    config = function(_, opts)
      require("project_nvim").setup(opts)
    end,
}

Does it need any other config?

Edit: It is supposed to set detection_methods and patterns automatically, but I also tried adding them explicitly to opts without any effect (get_recent_projects still returns an empty table).

@kkrime
Copy link
Author

kkrime commented Mar 3, 2025

@magnusriga
Copy link

@kkrime Thanks for helping me. I found the issue:

  1. copilot.nvim uses cwd as LSP client root, which results in project.nvim never updating its list. That is wrong, in my opinion, so I filed an Issue at that repo. When I added a temporary fix for that root directory, this repo also started working as it should. I also noticed that I could have excluded copilot LSP client witht the ignore_client setting, but decided to instead try to fix that LSP client.
  2. As a side note, I tried your fork but realized that when you fixed the filetype bug in the original repo, the whitelist made it so that no filetypes were accepted. As sucn, I went back to the original repo, which now works as expected due to (1).

Hope that is helpful to others in the future. Thanks again for being so responsive.

@DrKJeff16
Copy link

DrKJeff16 commented Aug 11, 2025

@kkrime , @magnusriga

It looks like the maintainer hasn't been active for two years. I created a fork I'm actively maintaining and would love to have your enhancement.

That is, of course, if you want to.

@DrKJeff16
Copy link

DrKJeff16 commented Aug 11, 2025

@kkrime I am using lazy.nvim and did:

  {
    "kkrime/project.nvim",
    opts = { },
    event = "VeryLazy",
    config = function(_, opts)
      require("project_nvim").setup(opts)
    end,
}

Does it need any other config?

Edit: It is supposed to set detection_methods and patterns automatically, but I also tried adding them explicitly to opts without any effect (get_recent_projects still returns an empty table).

@magnusriga I am unsure whether you have this issue, but I've found Lazy-Loading this plugin can lead to buggy behaviour.

Lazy-Loading seems to be fixed in my fork.

@kkrime
Copy link
Author

kkrime commented Aug 12, 2025

@kkrime , @magnusriga

It looks like the maintainer hasn't been active for two years. I created a fork I'm actively maintaining and would love to have your enhancement.

That is, of course, if you want to.

Hey @DrKJeff16, sure feel free to use my code, no worries

@kkrime I am using lazy.nvim and did:

  {
    "kkrime/project.nvim",
    opts = { },
    event = "VeryLazy",
    config = function(_, opts)
      require("project_nvim").setup(opts)
    end,
}

Does it need any other config?
Edit: It is supposed to set detection_methods and patterns automatically, but I also tried adding them explicitly to opts without any effect (get_recent_projects still returns an empty table).

@magnusriga I am unsure whether you have this issue, but I've found Lazy-Loading this plugin can lead to buggy behaviour.

I'm not sure what the issue yoru facing is, but here's how I run it;

      {
        'ahmedkhalf/project.nvim',
        config = function()
          require("project_nvim").setup({
            -- line below commented out due to - https://github.com/ahmedkhalf/project.nvim/issues/169
            detection_methods = { "lsp", "pattern" },
            patterns = { ".git", ".gitignore", "README.md", "go.mod", "Makefile" },

            silent_chdir = false,
          })
        end
      },

I would remove event = "VeryLazy", that might be preventing the plugin from loading which might just be the issue.

Good luck 👍

@DrKJeff16
Copy link

Hey @DrKJeff16, sure feel free to use my code, no worries

@kkrime
Awesome! Will credit you for it, needless to say.


I'm not sure what the issue yoru facing is, but here's how I run it;

      {
        'ahmedkhalf/project.nvim',
        config = function()
          require("project_nvim").setup({
            -- line below commented out due to - https://github.com/ahmedkhalf/project.nvim/issues/169
            detection_methods = { "lsp", "pattern" },
            patterns = { ".git", ".gitignore", "README.md", "go.mod", "Makefile" },

            silent_chdir = false,
          })
        end
      },

I would remove event = "VeryLazy", that might be preventing the plugin from loading which might just be the issue.

Funny thing is I just did some new refactoring today that seemingly fixes that behaviour. Not intentionally, lol.

I'm honestly not sure yet about what piece of code is involved in this.

Essentially, event = 'VeryLazy' appears to be fine now 👀

@magnusriga
Copy link

Hey guys,

I can't remember what I ended up doing here. I think I moved to the same solution that LazyVim uses, which might have been some snacks.nvim submodule or similar.

I will check back in if it stops working.

Thanks for the effort and input 🥇🚀

local buf_name_to_file_path_map = {}

vim.api.nvim_create_autocmd("BufDelete", {
pattern = "*",
Copy link

@DrKJeff16 DrKJeff16 Aug 12, 2025

Choose a reason for hiding this comment

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

It's really convenient to use augroups:

local group = vim.api.nvim_create_augroup("ProjectTest", { clear = true })
vim.api.nvim_create_autocmd("BufDelete", {
  group = group,
  pattern = "*",
  --- ...

Copy link
Author

Choose a reason for hiding this comment

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

What would the value in using augroups be?

Choose a reason for hiding this comment

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

Mostly bundling augroups into categories. Makes them easier to find (i.e. when using vim.api.nvim_exec_autocmds() and so on.

For example if you want to run an autocmd through the cmdline,
you don't want to run ALL the, say, "BufAdd" autocmds. You might want to run just the ones related to, let's say, gitsigns as an arbitrary example.

M.last_project = nil

---@alias buf_name string this is the name of the buffer (the path/filename that is opened in the buffer)
---@alias buf_file_location string this is the buf_name minus the filename (just the path)

Choose a reason for hiding this comment

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

Why not document the values that use this alias instead of "wrapping" an alias type around string? I get what you want to do but in the long run it might confuse people.


--- regex is relatively expensive, so store buf_file_path in map
---@type table<buf_name, buf_file_location>
local buf_name_to_file_path_map = {}

Choose a reason for hiding this comment

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

Having ad-hoc types, in my personal opinion, could be an issue for understanding code.

local buf_name_to_file_path_map = {}

vim.api.nvim_create_autocmd("BufDelete", {
pattern = "*",

Choose a reason for hiding this comment

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

Mostly bundling augroups into categories. Makes them easier to find (i.e. when using vim.api.nvim_exec_autocmds() and so on.

For example if you want to run an autocmd through the cmdline,
you don't want to run ALL the, say, "BufAdd" autocmds. You might want to run just the ones related to, let's say, gitsigns as an arbitrary example.

root_dir = folder_name
elseif #folder_name > #root_dir then
root_dir = folder_name
end

Choose a reason for hiding this comment

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

I suggest two alternatives:

Simple Conditional

if not (root_dir and #folder_name <= #root_dir) then
  root_dir = folder_name
end

Using Ternary Operators

root_dir = not (root_dir and #folder_name <= #root_dir) and folder_name or root_dir

This one acts like a ternary operator.1


The reason I'm doing not (root_dir and #folder_name <= #root_dir) is a simple grouping of logical conditions.

Expanding it would look like not root_dir or not #folder_name <= #root_dir.

The inverse of not #folder_name <= #root_dir is #folder_name > #root_dir.


Footnotes

  1. https://stackoverflow.com/questions/5525817/inline-conditions-in-lua-a-b-yes-no#answer-72021612


-- Fall back to root_dir
return client.config.root_dir
end

Choose a reason for hiding this comment

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

An alternative is to kill the if root_dir then code block and use this return statement:

  return not (root_dir) and client_config.root_dir or root_dir
end

local clients = vim.lsp.buf_get_clients()
local buf_ft = vim.api.nvim_get_option_value('filetype', { buf = 0 })
local buf_name = vim.api.nvim_buf_get_name(0)
local clients = vim.lsp.get_clients({ bufnr = 0 })

Choose a reason for hiding this comment

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

I don't recommend using 0 as bufnr inside a function. An alternative:

  local bufnr = vim.api.nvim_get_current_buf()

  local buf_ft = vim.api.nvim_get_option_value('filetype', { buf = bufnr })
  local buf_name = vim.api.nvim_buf_get_name(bufnr)
  local clients = vim.lsp.get_clients({ bufnr = bufnr })

if not vim.tbl_contains(config.options.ignore_lsp, client.name) then
return client.config.root_dir, client.name
local lsp_root = M._lsp_get_buf_root(client, buf_name)
return lsp_root, client.name

Choose a reason for hiding this comment

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

Suggestion:

        return M._lsp_get_buf_root(client, buf_name), client.name

if client.workspace_folders then
for _, workspace_folder in pairs(client.workspace_folders) do
local folder_name = vim.uri_to_fname(workspace_folder.uri)
if folder_name and vim.startswith(buf_file_path, folder_name) then

Choose a reason for hiding this comment

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

I suggest inverting this conditional to reduce the complexity:

if not (folder_name or vim.startswith(buf_file_path, folder_name)) then
  --- Fail case
end

@DrKJeff16
Copy link

DrKJeff16 commented Aug 13, 2025

@kkrime

Are you interested in recreating this PR in my fork? This repo's author seems unavailable for the foreseeable future.

Also some of the suggested fixes have been adressed there, hopefully making your integration easier to write.

@kkrime
Copy link
Author

kkrime commented Aug 16, 2025

@kkrime

Are you interested in recreating this PR in my fork? This repo's author seems unavailable for the foreseeable future.

Also some of the suggested fixes have been adressed there, hopefully making your integration easier to write.

Ya sure, I noticed you've done a lot of refactoring, so I might have a few questions, but ya, I'll get on to it when I get time

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.

4 participants