telescope.nvim icon indicating copy to clipboard operation
telescope.nvim copied to clipboard

find_files and grep in selected directory

Open princejoogie opened this issue 1 year ago • 36 comments

Is your feature request related to a problem? Please describe.

the builtin grep and find_files searches the current directory. in cases when you're working in a monorepo or a project with multiple apps, it may be harder to narrow down the searches with the builtin grep and find_files. are there plans to support these functions natively?

Describe the solution you'd like

i have a working library which can do this (dir-telescope.nvim). i just wanted to know if there are future plans to directly implement this in telescope.nvim, thanks

princejoogie avatar Oct 19 '22 11:10 princejoogie

You can achieve this with telescope-file-browser and a custom function.

This action will launch telescope-file-browser in folder mode, and then launch live_grep within the selected directory:

local ts_select_dir_for_grep = function(prompt_bufnr)
  local action_state = require("telescope.actions.state")
  local fb = require("telescope").extensions.file_browser
  local live_grep = require("telescope.builtin").live_grep
  local current_line = action_state.get_current_line()

  fb.file_browser({
    files = false,
    depth = false,
    attach_mappings = function(prompt_bufnr)
      require("telescope.actions").select_default:replace(function()
        local entry_path = action_state.get_selected_entry().Path
        local dir = entry_path:is_dir() and entry_path or entry_path:parent()
        local relative = dir:make_relative(vim.fn.getcwd())
        local absolute = dir:absolute()

        live_grep({
          results_title = relative .. "/",
          cwd = absolute,
          default_text = current_line,
        })
      end)

      return true
    end,
  })
end

The action also captures the current line from telescope, because it's meant to be launched from within live_grep:

telescope.setup({
  pickers = {
    live_grep = {
      mappings = {
        i = {
          ["<C-f>"] = ts_select_dir_for_grep,
        },
        n = {
          ["<C-f>"] = ts_select_dir_for_grep,
        },
      },
    },
  },
})

So the flow is:

  1. Launch live_grep
  2. Start typing
  3. Realize that you need to scope to directory
  4. <Ctrl-f>
  5. Select directory
  6. Get returned to live_grep, scoped to the directory you selected and with your search args still populated

asciicast

You can repeat 3-6 as many times as needed.

(If you use live_grep_args, replace require("telescope.builtin").live_grep with require("telescope").extension.live_grep_args.live_grep)

schoblaska avatar Oct 19 '22 23:10 schoblaska

The above solution is of course much nicer, but one can simply specify the search_dirs to limit the scope of the search.

:Telescope find_files search_dirs={"/path/to/relevant/folder/"}

sotte avatar Nov 06 '22 09:11 sotte

What would be even nicer than spending time figuring out how to make this work and then writing all the above code that uses an extension would be if this functionality was builtin.

briandipalma avatar Nov 11 '22 13:11 briandipalma

I find myself constantly running into this, especially in monorepos. With all 3 of my most used pickers: file picker, text search and live grep. I would also much prefer to have this feature natively instead of adding more plugin and script bloat. Telescope already does almost everything I personally need from it and hope it could extend to this as well at some point. It's an extremely useful utility for me.

Example scenario:

  • Work in /something/service-a/src
  • Remember you did this "thing" in an another app
  • Launch
  • Look for "thing"
  • Notice you cannot look into /something/service-b/src
  • Open split terminal
  • $ cd ../../service-b/src
  • $ rg "thing"
  • ...

I would prefer this workflow:

  • Work in /something/service-a/src
  • Remember you did this "thing" in an another app
  • Launch
  • Swap to "change active directory" picker momentarily: <some-mapping>
  • Browse to correct directory
  • Look for "thing"
  • ...

I imagine directory picker would be a normal file browser with traversal capabilities (<CR> / <C-CR>|<BS> to traverse) that shows directories on top and files inside the currently viewed directory under those with some mapping changing the current working directory to the one under the cursor and swapping back to previous picker (<some-mapping> again), possibly an another mapping for changing the working directory for all future pickers as well (<SOME-MAPPING> maybe?).

../
service-a/
service-b/
foo.txt
bar.sh

One can dream but perhaps someone well versed with the plugin might get a spark from this idea. Thanks for all you've done already, love the plugin!

puttehi avatar Mar 14 '23 21:03 puttehi

For grep in monorepo, I have replaced live_grep with live_grep_args. Do we have a similar thing for find_files for example find_files_args ? I know about the search_dirs option but it is not very convenient to use.

pr7prashant avatar Mar 27 '23 02:03 pr7prashant

The dir-telescope plugin linked in the OP is what I'd recommend. It should really be builtin to telescope.

briandipalma avatar Mar 27 '23 21:03 briandipalma

If you use telescope-file-browser you can also use an custom action like below from within a directory to live_grep/find_files for things only in that directory.

local Path = require("plenary.path")
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")

local open_using = function(finder)
  return function(prompt_bufnr)
    local current_finder = action_state.get_current_picker(prompt_bufnr).finder
    local entry = action_state.get_selected_entry()

    local entry_path
    if entry.ordinal == ".." then
      entry_path = Path:new(current_finder.path)
    else
      entry_path = action_state.get_selected_entry().Path
    end

    local path = entry_path:is_dir() and entry_path:absolute() or entry_path:parent():absolute()
    actions.close(prompt_bufnr)
    finder({ cwd = path })
  end
end

-- map keys to for file_browser to...
open_using(builtin.find_files),
open_using(builtin.live_grep),

jamestrew avatar Mar 27 '23 21:03 jamestrew

I tried some of the solutions mentioned above today. I like the idea of scoping the search to a directory usingtelescope-file-browser but it only allows me to scope within the current directory. Is there a way to increase the scope of search? For example if I want to add the parent directory to the scope of search as well (monorepo use case as mentioned above by puttehi also). That means if I am inside package-a and I want to search something in package-b or in the entire packages folder.

../
packages
  package-a/
    package.json
  package-b/
    package.json
package.json

Thanks for the help, much appreciated :)

pr7prashant avatar Apr 02 '23 15:04 pr7prashant

dir-telescope plugin linked in the OP allows you to make multiple selections and you can then search through all those directories at the same time, is that what you mean?

briandipalma avatar Apr 03 '23 21:04 briandipalma

../
packages
  package-a/
    package.json
  package-b/
    package.json
package.json

Hi Brian. Actually, both the plugins were working similarly for me. Suppose I take the above folder structure as an example. If my current buffer is packages/package-a/package.json and I do the FileInDirectory, it cannot find packages/package-b/package.json. It searches in the scope of packages/package-a/. I wanted to search in the scope of packages/ when my current buffer is packages/package-a/package.json.

I'll try to add a reproducible demo link here and probably I should also ask this query in the dir telescope repo. Sorry, I'm new to vim/neovim and it is also possible that I have made a blunder :)

pr7prashant avatar Apr 04 '23 02:04 pr7prashant

Are you using LazyVim? It sounds like you are running search in root dir (which is the package/project dir) instead of cwd (which is where you opened up Neovim (at the root of the monorepo I'm guessing).

https://www.lazyvim.org/keymaps

briandipalma avatar Apr 04 '23 14:04 briandipalma

I am using Neovim without LazyVim. And yes. I open Neovim at the root of the monorepo. From the root level I can grep/find_file everything but when I open a file inside any package, grep/find_file only works in the scope of that package.

pr7prashant avatar Apr 04 '23 15:04 pr7prashant

I think something similar is happening to you, the root dir is being passed in somewhere and that's why your searches are being rooted there. No idea how it's happening you'll have to dig into your config to figure it out.

briandipalma avatar Apr 04 '23 20:04 briandipalma

Finally found the issue in my config. I was using project.nvim plugin which integrates with telescope. In the config of that plugin, I was using package.json as a pattern to detect the project's root. Since in a JS mono repo, all the packages have their own package.json, the live_grep/find_files was getting rooted to the current package.

pr7prashant avatar Apr 16 '23 10:04 pr7prashant

I have the same issue as @pr7prashant : I would sometimes like to search outside of the current root. One specific use case that I'm running into right now is that as I'm setting up my configuration, I frequently want to search for where certain mappings/plugins are set up by grepping inside the plugin folder, e.g. ~/.local/nvim/share/lazy. I am using LazyVim which has options for cwd and root dir as @briandipalma said, but this is folder will pretty much always lie outside the scope of these directories.

I tried passing in an option to the dir-telescope.nvim plugin, but it doesn't appear to support arguments. Any ideas for how to accomplish this?

@jamestrew how does your solution differ from the one above by schoblaska, which both use telescope-file-browser?

albertfgu avatar Apr 25 '23 03:04 albertfgu

You could take a look at LazyVim's keybindings and add your own specifying that one directory as the cwd?

briandipalma avatar Apr 27 '23 10:04 briandipalma

I want to be able to live_grep some string and then, while I have results, further filter the results to only those in a specific subdirectory. Is that not possible?

thallada avatar Jul 11 '23 18:07 thallada

@thallada not exactly what you're asking for but you can <C-space> to refine the search with fuzzy matching from within the live_grep picker

jamestrew avatar Jul 12 '23 00:07 jamestrew

@schoblaska I know it's been a while, but I am trying to use your suggested solution. I used it with normal live_grep without issues, but I want to switch to live_grep_args. So I updated require("telescope.builtin").live_grep with require("telescope").extension.live_grep_args.live_grep and added a mapping:

  extensions = {
    live_grep_args = {
      mappings = {
        i = {
          ["<C-p>"] = ts_select_dir_for_grep,
        },
      },
    },
  }

But when I try to invoke the filebrowser I get this error:

E5108: Error executing lua: .../.config/nvim/lua/plugins/nvim-telescope.lua:16: attempt to index field 'extension' (a nil value)
stack traceback:
        .../.config/nvim/lua/plugins/nvim-telescope.lua:16: in function 'key_func'
        ...k/packer/start/telescope.nvim/lua/telescope/mappings.lua:257: in function <...k/packer/start/telescope.nvim/lua/telescope/mappings.lua:256>

Where line 16 is line 5 in the function:

local ts_select_dir_for_grep = function(_)
  local action_state = require("telescope.actions.state")
  local current_line = action_state.get_current_line()
  local fb = require("telescope").extensions.file_browser
  local live_grep = require("telescope").extension.live_grep_args.live_grep -- this is line 16

  fb.file_browser({
    files = false,
    depth = false,
    attach_mappings = function(_)
      require("telescope.actions").select_default:replace(function()
        local entry_path = action_state.get_selected_entry().Path
        local dir = entry_path:is_dir() and entry_path or entry_path:parent()
        local relative = dir:make_relative(vim.fn.getcwd())

        live_grep({
          cwd = dir:absolute(),
          default_text = current_line,
          results_title = relative .. "/",
        })
      end)

      return true
    end,
  })
end

For all other usecases the filebrowser works as expected, so any pointer on what I missed? Thanks!!

EDIT: Also tried it with require("telescope").extension.live_grep_args.live_grep_args but gives the same result

svanharmelen avatar Jul 27 '23 18:07 svanharmelen

@svanharmelen

attempt to index field 'extension' (a nil value)

The error says that require("telescope").extension is nil. It should be require("telescope").extensions (with an "s").

I also use live_grep_args. Here's the function I use:

select_dir_for_grep = function(prompt_bufnr)
  local action_state = require("telescope.actions.state")
  local fb = require("telescope").extensions.file_browser
  local lga = require("telescope").extensions.live_grep_args
  local current_line = action_state.get_current_line()

  fb.file_browser({
    files = false,
    depth = false,
    attach_mappings = function(prompt_bufnr)
      require("telescope.actions").select_default:replace(function()
        local entry_path = action_state.get_selected_entry().Path
        local dir = entry_path:is_dir() and entry_path or entry_path:parent()
        local relative = dir:make_relative(vim.fn.getcwd())
        local absolute = dir:absolute()

        lga.live_grep_args({
          results_title = relative .. "/",
          cwd = absolute,
          default_text = current_line,
        })
      end)

      return true
    end,
  })
end

schoblaska avatar Jul 27 '23 18:07 schoblaska

Ahh... 🙈 Thanks for your quick response @schoblaska! Works like a charm now 👍🏻

svanharmelen avatar Jul 27 '23 19:07 svanharmelen

So I'm pretty sure everything worked as expected 3 days ago, but now I'm having an issue again. If I open fb directly it works fine:

image

But if I open fb from the live_grep window it shows me this:

image

So the two test dirs are now in the preview windows instead of in the picker window and I have nothing to select from in the picker window. Trying to type doesn't change anything, so I'm not able to select any directory now.

I just now copied the exact function you pasted 3 days ago, but that gives me the same result. Any idea what may cause this behavior? Did anything change (I did update all my plugins)?

For completeness, the rest of my Telescope config looks like this:

image

svanharmelen avatar Jul 30 '23 15:07 svanharmelen

@svanharmelen Does folder browser (:Telescope file_browser files=false) work if you open it by itself? If not, it might be an issue with telescope-file-browser.

schoblaska avatar Jul 30 '23 15:07 schoblaska

Thanks again for your quick response @schoblaska! I just reverted the last commit from 3 days ago, and that fixed the problem. So its indeed a bug with the file-browser plugin 😏 Will open an issue there to see if they can get it sorted.

svanharmelen avatar Jul 30 '23 15:07 svanharmelen

@thallada not exactly what you're asking for but you can <C-space> to refine the search with fuzzy matching from within the live_grep picker

@jamestrew are you sure this is correct? I don't see this binding in the telescope default bindings.

dudicoco avatar Feb 21 '24 12:02 dudicoco

@dudicoco It's here: https://github.com/nvim-telescope/telescope.nvim/blob/b744cf59752aaa01561afb4223006de26f3836fd/lua/telescope/builtin/__files.lua#L180

briandipalma avatar Feb 21 '24 22:02 briandipalma

thanks @briandipalma. this binding feels like the best solution to the issue.

dudicoco avatar Feb 22 '24 13:02 dudicoco

VSC*ode search has include/exclude dir. Would be nice to be able to do something like this with Telescope.

1

gitkumi avatar Mar 15 '24 15:03 gitkumi

@gitkumi Have you tried the refine search feature triggered with C-space and installing https://github.com/nvim-telescope/telescope-fzf-native.nvim ? I find by using ! to remove what I don't need and refining with more terms (for example using ^) I get quite close to what I need.

So I'd start searching for my-search-term, then I press C-space and the term is "consumed" by the picker (it becomes the Find word (my-search-term) you see below), so the input is empty. I can then filter out files like test files by writing !test and lets say less files with !.less and filter in a directory by typing ^from/root/my/directory so I'd type !test !.less ^from/root/my/directory in the input and I get what this issue is requesting. Does that make sense? Just add a space between the different search terms.

image

Frankly I think this issue can be closed as I think this is what people are looking for it just needed better documentation.

briandipalma avatar Mar 17 '24 08:03 briandipalma

I wanted to test how this works, so added the fzf-native plugin but <C-space> doesn't seem to do anything. Is there something I should enable/disable/configure for that to work? Tried it with find_files, live_grep and file_browser, all the same result (nothing happens).

Thanks!

svanharmelen avatar Mar 18 '24 07:03 svanharmelen