DEV Community

Cover image for nvim: Telescope toggle cwd
Sérgio Araújo
Sérgio Araújo

Posted on

nvim: Telescope toggle cwd

Intro

Being abble to change the scope of a srarch, grep or other commands gives us more control.

Finding the root dir

--- Returns project_root
--- @param bufnr number|nil Buffer
--- @return string root dir
M.root = function(bufnr)
  bufnr = bufnr or 0
  local markers = {
    '.git',
    '.hg',
    '.svn',
    'pyproject.toml',
    'setup.py',
    'requirements.txt',
    'package.json',
    'tsconfig.json',
    'Makefile',
    'CMakeLists.txt',
    '.nvim-root',
  }

  return vim.fs.root(bufnr, markers) or vim.fn.getcwd()
end
Enter fullscreen mode Exit fullscreen mode

Custom telescope find_files

This module will be called via keymap

-- File: ~/.config/nvim/lua/core/utils/telescope.lua
-- Last Change: 2025-06-23
-- Author: Sergio Araujo

local M = {}

local actions = require('telescope.actions')
local pickers = require('telescope.pickers')
local finders = require('telescope.finders')
local conf = require('telescope.config').values
local ts_utils = require('telescope.utils')
local builtin = require('telescope.builtin')
local utils = require('telescope.utils')

local get_root = require('core.utils.file_system').root

-- Função para path_display que remove prefixo Termux e mantém estilo tail+path
local function oldfiles_path_display(_, path)
  local termux_prefix = '/data/data/com.termux/files/home/'
  if path:sub(1, #termux_prefix) == termux_prefix then path = path:sub(#termux_prefix + 1) end

  local tail = utils.path_tail(path)
  local display = string.format('%s %s', tail, path)
  local hl_start = #tail + 1
  local hl_end = #display

  return display, { { { hl_start, hl_end }, 'Comment' } }
end

-- Garante um diretório válido mesmo que buffer esteja vazio
local function get_valid_buf_dir()
  local dir = ts_utils.buffer_dir()
  return (dir and dir ~= '') and dir or vim.loop.cwd()
end

-- Find files com toggle de cwd
M.find_files_with_toggle = function()
  local root = get_root()
  if not root or root == '' then
    vim.notify('Root dir não encontrado. Você está fora de um projeto?', vim.log.levels.WARN, {
      title = 'Find Files',
    })
    return
  end

  local buf_dir = get_valid_buf_dir()
  local current_cwd = root

  local function picker()
    builtin.find_files({
      cwd = current_cwd,
      attach_mappings = function(prompt_bufnr, map)
        map('i', '<a-d>', function()
          actions.close(prompt_bufnr)
          local new_cwd = (current_cwd == root) and buf_dir or root
          local title = 'Find Files'

          if new_cwd ~= current_cwd then
            current_cwd = new_cwd
            vim.notify('cwd switched to: ' .. current_cwd, vim.log.levels.INFO, { title = title })
          else
            vim.notify('cwd não foi alterado (já em ' .. current_cwd .. ')', vim.log.levels.WARN, { title = title })
          end

          picker()
        end)
        return true
      end,
    })
  end

  picker()
end

-- Grep customizado com toggle de cwd e preservação do prompt
M.custom_grep_with_toggle = function()
  local root = get_root()
  if not root or root == '' then
    vim.notify('Root dir não encontrado. Você está fora de um projeto?', vim.log.levels.WARN, {
      title = 'Custom Grep',
    })
    return
  end

  local buf_dir = get_valid_buf_dir()
  local current_cwd = root
  local current_prompt = ''

  local function picker()
    pickers
      .new({}, {
        prompt_title = 'Custom Grep',
        finder = finders.new_job(function(prompt)
          if prompt == '' then return nil end
          current_prompt = prompt
          return { 'rg', '--vimgrep', '--no-heading', prompt }
        end, nil, { cwd = current_cwd }),
        previewer = conf.grep_previewer({}),
        sorter = conf.generic_sorter({}),
        default_text = current_prompt,
        attach_mappings = function(prompt_bufnr, map)
          map('i', '<a-d>', function()
            actions.close(prompt_bufnr)
            local new_cwd = (current_cwd == root) and buf_dir or root
            local title = 'Custom Grep'

            if new_cwd ~= current_cwd then
              current_cwd = new_cwd
              vim.notify('cwd switched to: ' .. current_cwd, vim.log.levels.INFO, { title = title })
            else
              vim.notify('cwd não foi alterado (já em ' .. current_cwd .. ')', vim.log.levels.WARN, { title = title })
            end

            picker()
          end)
          return true
        end,
      })
      :find()
  end

  picker()
end

-- Oldfiles com path_display customizado para Termux
M.oldfiles_clean = function()
  builtin.oldfiles({
    path_display = oldfiles_path_display,
    layout_strategy = 'vertical', -- ou "horizontal", conforme preferir
    layout_config = {
      height = 0.5, -- aqui você diminui a altura (0.3 = 30% da tela)
      width = 0.7, -- opcional: ajustar largura também
      prompt_position = 'top',
      preview_cutoff = 1, -- para esconder preview em telas pequenas
    },
  })
end

return M
Enter fullscreen mode Exit fullscreen mode

The keymaps

In your telescope keys table place

-- Adjust the module path to reflect you realitty
{
      '<leader>ff',
      function() require('core.utils.telescope').find_files_with_toggle() end,
      desc = 'Find Files (toggle raiz/buffer com Alt+D)',
},
{
      '<leader>fg',
      function() require('core.utils.telescope').custom_grep_with_toggle() end,
      desc = 'Custom Grep (rg) toggle root/buffer com <A-d>',
},
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
gokayburuc profile image
gokayburuc.dev

First of all, thank you for sharing your code and your ideas. Since there are different levels of users on this platform, from basic to senior, if you briefly state in the title of the article who the article is written for and how they will use it, you can help people get an idea about the article before moving on.

My second suggestion is that if you do not want to write a long article but want to explain your code; After pasting your script via ChatGPT, you can make your code more understandable for those who are not Lua literate with the help of the "Break apart the code and add line by line comments" prompt. In this way, it will be a clearer sharing and will be open to development.

Collapse
 
gokayburuc profile image
gokayburuc.dev

ChatGPT code break apart sample for your first script:


Here's a line-by-line breakdown of your Lua function with inline comments and explanations:

--- Returns project_root
--- @param bufnr number|nil Buffer
--- @return string root dir
Enter fullscreen mode Exit fullscreen mode
  • These are annotations (commonly used in LSP or documentation generators).
  • bufnr: Optional buffer number.
  • Return: A string representing the root directory of the project.

M.root = function(bufnr)
Enter fullscreen mode Exit fullscreen mode
  • Define a function root in table M.
  • It accepts an optional parameter bufnr (buffer number).

  bufnr = bufnr or 0
Enter fullscreen mode Exit fullscreen mode
  • If bufnr is not provided (nil), default to 0, which refers to the current buffer.

  local markers = {
    '.git',
    '.hg',
    '.svn',
    'pyproject.toml',
    'setup.py',
    'requirements.txt',
    'package.json',
    'tsconfig.json',
    'Makefile',
    'CMakeLists.txt',
    '.nvim-root',
  }
Enter fullscreen mode Exit fullscreen mode
  • Define a list of root markers — files or folders that are typically found in a project's root.
  • These include common VCS folders (.git, .hg, .svn) and build/config files for Python, Node.js, C/C++, etc.

  return vim.fs.root(bufnr, markers) or vim.fn.getcwd()
Enter fullscreen mode Exit fullscreen mode
  • Use vim.fs.root(bufnr, markers) to find the nearest ancestor directory of the buffer that contains one of the markers.
  • If no such directory is found, fall back to the current working directory returned by vim.fn.getcwd().

Summary

This function:

  • Tries to locate the project root based on common marker files or directories.
  • Falls back to the current working directory if none are found.
  • Useful for plugins, file navigation, and language server configuration in Neovim.

Related Keywords

neovim, vim.fs.root, project root detection, lua, vim.fn.getcwd, LSP, buffer, marker files, file system traversal.

Collapse
 
voyeg3r profile image
Sérgio Araújo

Fantastic suggestion! I really apreciate!

Thread Thread
 
gokayburuc profile image
gokayburuc.dev

Thank you for your understanding. We are here to support each other to improve our common knowledge. I'm using this special technique to analyze Github Repositories (dotfiles, Rust Repos etc.). It helps me to understand the code quickly. See you at your new article Sergio!

Keep writing, keep sharing, keep learning ;)