main 21b7b7950328 cached
23 files
75.6 KB
22.3k tokens
1 requests
Download .txt
Repository: dhananjaylatkar/cscope_maps.nvim
Branch: main
Commit: 21b7b7950328
Files: 23
Total size: 75.6 KB

Directory structure:
gitextract_qp_pu4jg/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── panvimdoc.yml
├── LICENSE
├── README.md
├── doc/
│   └── cscope_maps.txt
└── lua/
    ├── cscope/
    │   ├── db.lua
    │   ├── init.lua
    │   ├── pickers/
    │   │   ├── fzf-lua.lua
    │   │   ├── location.lua
    │   │   ├── mini-pick.lua
    │   │   ├── quickfix.lua
    │   │   ├── snacks.lua
    │   │   └── telescope.lua
    │   └── stack_view/
    │       ├── hl.lua
    │       ├── init.lua
    │       └── tree.lua
    └── cscope_maps/
        ├── init.lua
        └── utils/
            ├── helper.lua
            ├── init.lua
            ├── log.lua
            └── ret_codes.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: dhananjaylatkar
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: dhananjaylatkar

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**nvim version (please complete the following information):**
nvim -v

**cscope_maps config (please complete the following information):**
```lua
opts = {}
```

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: dhananjaylatkar

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/workflows/panvimdoc.yml
================================================
name: panvimdoc

on:
  push:
    paths:
      - "README.md"

jobs:
  docs:
    runs-on: ubuntu-latest
    name: pandoc to vimdoc
    steps:
      - uses: actions/checkout@v2
      - name: panvimdoc
        uses: kdheepak/panvimdoc@main
        with:
          vimdoc: cscope_maps
          pandoc: "README.md"
          version: "Neovim >= v0.10.0"
          treesitter: true
          demojify: true
      - name: Push changes
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: "docs: auto generate"
          branch: ${{ github.head_ref }}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Dhananjay

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# cscope_maps.nvim

For old school code navigation :)

Heavily inspired by emacs' [xcscope.el](https://github.com/dkogan/xcscope.el).

**Adds `cscope` support for [Neovim](https://neovim.io/)**

[cscope_maps.nvim.v2.webm](https://github.com/dhananjaylatkar/cscope_maps.nvim/assets/27724944/7ab4d902-fe6d-4914-bff6-353136c72803)

## Requirements

- Neovim >= 0.10
- [cscope](https://sourceforge.net/projects/cscope/files/)

## Features

### Cscope

- Tries to mimic vim's builtin cscope functionality.
- Provides user command, `:Cscope` which acts same as good old `:cscope`.
- Short commands are supported. e.g. `:Cs f g <sym>`
- `:Cstag <sym>` does `tags` search if no results are found in `cscope`.
- Empty `<sym>` can be used in `:Cs` and `:Cstag` to pick `<cword>` as sym.
- Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable.
- Keymaps can be disabled using `disable_maps` option.
- `:CsPrompt <op>` can be used to invoke cscope prompt.
- Display results in quickfix list, location list, **telescope**, **fzf-lua**, **mini.pick** or **snacks.nvim**.
- Has [which-key.nvim](https://github.com/folke/which-key.nvim) and [mini.clue](https://github.com/echasnovski/mini.clue) hints.
- See [this section](#vim-gutentags) for `vim-gutentags`.

### Cscope DB

- Add DB files
  - Statically provide table of db paths in config (`db_file`)
  - `db_file` opt can be `function`. This function __must__ return table of db paths. This can be used to conditionally (e.g. based on `cwd`) add db paths.
  - Add DB file at runtime using `:Cs db add ...`
    - `:Cs db add <space sepatated files>` add db file(s) to cscope search.
    - `:Cs db rm <space sepatated files>` remove db file(s) from cscope search.
- `:Cs db show` show all db connections.
- `:Cs db build` (re)builds db.
  - If `db_build_cmd.script == "default"` then only primary DB will be built using cscope binary.
    - e.g. `cscope -f ${db_file} ${db_build_cmd.args}` OR `gtags-cscope ${db_build_cmd.args}`
  - Custom script can be provided using `db_build_cmd.script`. Example script is [here](https://github.com/dhananjaylatkar/cscope_maps.nvim/pull/67)
    - e.g. user script will be called as following -
    - `${db_build_cmd.script} ${db_build_cmd.args} -d <db1>::<pre_path1> -d <db2>::<pre_path2> ...`
- `vim.g.cscope_maps_statusline_indicator` can be used in statusline to indicate ongoing db build.
- DB path grammar
  - `db_file::db_pre_path` db_pre_path (prefix path) will be appended to cscope results.
  - e.g. `:Cs db add ~/cscope.out::/home/code/proj2` => results from `~/cscope.out` will be prefixed with `/home/code/proj2/`
  - `@` can be used to indicate that parent of `db_file` is `db_pre_path`.
  - e.g. `:Cs db add ../proj2/cscope.out::@` => results from `../proj2/cscope.out` will be prefixed with `../proj2/`

### Stack View

- Visualize tree of caller functions and called functions.
- `:CsStackView open down <sym>` Opens "downward" stack showing all the functions who call the `<sym>`.
- `:CsStackView open up <sym>` Opens "upward" stack showing all the functions called by the `<sym>`.
- In `CsStackView` window, use following keymaps
  - `<tab>` toggle child under cursor
  - `<cr>` open location of symbol under cursor
  - `<C-v>` open location of symbol under cursor in vertical split
  - `<C-s>` open location of symbol under cursor in horizontal split
  - `q` or `<esc>` close window
  - `<C-u>` or `<C-y>` scroll up preview
  - `<C-d>` or `<C-e>` scroll down preview
- `:CsStackView toggle` reopens last `CsStackView` window.
- In `CsStackView` window, all nodes that are part of current stack are highlighted.

## Installation

Install the plugin with your preferred package manager.
Following example uses [lazy.nvim](https://github.com/folke/lazy.nvim)

```lua
{
  "dhananjaylatkar/cscope_maps.nvim",
  dependencies = {
    "nvim-telescope/telescope.nvim", -- optional [for picker="telescope"]
    "ibhagwan/fzf-lua", -- optional [for picker="fzf-lua"]
    "echasnovski/mini.pick", -- optional [for picker="mini-pick"]
    "folke/snacks.nvim", -- optional [for picker="snacks"]
  },
  opts = {
    -- USE EMPTY FOR DEFAULT OPTIONS
    -- DEFAULTS ARE LISTED BELOW
  },
}
```

## Configuration

You must run `require("cscope_maps").setup()` to initialize the plugin even when using default options.

NOTE: In `vimrc` use `lua require("cscope_maps").setup()`

_cscope_maps_ comes with following defaults:

```lua
{
  -- maps related defaults
  disable_maps = false, -- "true" disables default keymaps
  skip_input_prompt = false, -- "true" doesn't ask for input
  prefix = "<leader>c", -- prefix to trigger maps

  -- cscope related defaults
  cscope = {
    -- location of cscope db file
    db_file = "./cscope.out", -- DB or table of DBs
                              -- NOTE:
                              --   when table of DBs is provided -
                              --   first DB is "primary" and others are "secondary"
                              --   primary DB is used for build and project_rooter
    -- cscope executable
    exec = "cscope", -- "cscope" or "gtags-cscope"
    -- choose your fav picker
    picker = "quickfix", -- "quickfix", "location", "telescope", "fzf-lua", "mini-pick" or "snacks"
    -- qf_window_size = 5, -- deprecated, replaced by picket_opts below, but still supported for backward compatibility
    -- qf_window_pos = "bottom", -- deprecated, replaced by picket_opts below, but still supported for backward compatibility
    picker_opts = {
      window_size = 5, -- any positive integer
      window_pos = "bottom", -- "bottom", "right", "left" or "top"
      -- options for Snacks picker (---@class snacks.picker.Config)
      -- pass-through options for Snacks picker
      snacks = {}, -- snacks config
    },
    -- "true" does not open picker for single result, just JUMP
    skip_picker_for_single_result = false, -- "false" or "true"
    -- custom script can be used for db build
    db_build_cmd = { script = "default", args = { "-bqkv" } },
    -- statusline indicator, default is cscope executable
    statusline_indicator = nil,
    -- try to locate db_file in parent dir(s)
    project_rooter = {
      enable = false, -- "true" or "false"
      -- change cwd to where db_file is located
      change_cwd = false, -- "true" or "false"
    },
    -- cstag related defaults
    tag = {
      -- bind ":Cstag" to "<C-]>"
      keymap = true, -- "true" or "false"
      -- order of operation to run for ":Cstag"
      order = { "cs", "tag_picker", "tag" }, -- any combination of these 3 (ops can be excluded)
      -- cmd to use for "tag" op in above table
      tag_cmd = "tjump",
    },
  },

  -- stack view defaults
  stack_view = {
    tree_hl = true, -- toggle tree highlighting
    size = "medium", -- "medium" or "large" (large is 95% of screen)
  }
}
```

## vim-gutentags

### Config for vim-gutentags

```lua
{
  "ludovicchabant/vim-gutentags",
  init = function()
    vim.g.gutentags_modules = {"cscope_maps"} -- This is required. Other config is optional
    vim.g.gutentags_cscope_build_inverted_index_maps = 1
    vim.g.gutentags_cache_dir = vim.fn.expand("~/code/.gutentags")
    
    -- ⚠️ WARNING: This limits tags to .c and .h files only, add more extensions (e.g., `-e py`) or remove to avoid skipping other files.
    vim.g.gutentags_file_list_command = "fd -e c -e h"
    
    -- vim.g.gutentags_trace = 1
  end,
}
```

### Alternative to vim-gutentags

Alternative to gutentags is to rebuild DB using `:Cscope db build` or `<prefix>b`.

You can create autocmd for running `:Cscope db build` after saving .c and .h files.
e.g

```lua
local group = vim.api.nvim_create_augroup("CscopeBuild", { clear = true })
vim.api.nvim_create_autocmd("BufWritePost", {
  pattern = { "*.c", "*.h" },
  callback = function ()
    vim.cmd("Cscope db build")
  end,
  group = group,
})
```

## Keymaps

### Default Keymaps

`<prefix>` can be configured using `prefix` option. Default value for prefix
is `<leader>c`.

(Try setting it to `C-c` 😉)

| Keymaps           | Description                                         |
| ----------------- | --------------------------------------------------- |
| `<prefix>s`       | find all references to the token under cursor       |
| `<prefix>g`       | find global definition(s) of the token under cursor |
| `<prefix>c`       | find all calls to the function name under cursor    |
| `<prefix>t`       | find all instances of the text under cursor         |
| `<prefix>e`       | egrep search for the word under cursor              |
| `<prefix>f`       | open the filename under cursor                      |
| `<prefix>i`       | find files that include the filename under cursor   |
| `<prefix>d`       | find functions that function under cursor calls     |
| `<prefix>a`       | find places where this symbol is assigned a value   |
| `<prefix>b`       | build cscope database                               |
| <kbd>Ctrl-]</kbd> | do `:Cstag <cword>`                                 |

### Custom Keymaps

Disable default keymaps by setting `disable_maps = true`.

There are 2 ways to add keymaps for `Cscope`.

#### Using `:CsPrompt` command

`:CsPrompt <op>` is user command to invoke prompt.
This command provides prompt which asks for input
before running `:Cscope` command.

e.g. Following snippet maps <kbd>C-c C-g</kbd> to find global def of symbol
under cursor

```lua
vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>CsPrompt g<cr>")
```

#### Using `:Cscope` command

e.g. Following snippet maps <kbd>C-c C-g</kbd> to find global def of symbol
under cursor

```lua
vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>Cs f g<cr>")
```

## Advanced Examples

### Using Snacks picker with custom options

Following snippet shows how to use `snacks.nvim` picker with custom layout.

`cscope.picker_opts.snacks` are passed as is to `Snacks.picker()`.
More details about `Snacks.picker()` can be found [here](https://github.com/folke/snacks.nvim/blob/main/docs/picker.md)

The layout used in this example is as follows -
 - Horizontal layout (input and list on the left, preview on the right)
 - Total width is 90% of screen width
 - Total height is 85% of screen height
 - The left side (input and list) takes up 60% of the width
 - The right side (preview) takes up 40% of the width
 - Rounded borders for input and preview windows
 - Preview window has word wrap enabled

```lua

-- only showing relevant part of the config
Opts = {
      cscope = {
        picker = 'snacks', -- snacks.picker (alternative: telescope)
        picker_opts = {
          ---@class snacks.picker.Config
          snacks = {
            -- layout = 'vertical', -- Use "vertical" or "horizontal" if you want to use presets
            ---@class snacks.picker.layout.Config
            layout = {
              layout = {
                height = 0.85, -- Take up 85% of the total height
                width = 0.9, -- Take up 90% of the total width (adjust as needed)
                box = 'horizontal', -- Horizontal layout (input and list on the left, preview on the right)
                { -- Left side (input and list)
                  box = 'vertical',
                  width = 0.6, -- List and input take up 60% of the width
                  border = 'rounded',
                  { win = 'input', height = 1, border = 'bottom' },
                  { win = 'list', border = 'none' },
                },
                { win = 'preview', border = 'rounded', width = 0.4 }, -- Preview window takes up 40% of the width
              },
            },
            ---@class snacks.picker.win.Config
            win = {
              preview = {
                wo = { wrap = true },
              },
            },
          }, -- snacks
        }, -- picker_opts
      }, -- cscope
    }, -- Opts

```



================================================
FILE: doc/cscope_maps.txt
================================================
*cscope_maps.txt*
                             For Neovim >= v0.10.0    Last change: 2026 May 07

==============================================================================
Table of Contents                              *cscope_maps-table-of-contents*

1. cscope_maps.nvim                             |cscope_maps-cscope_maps.nvim|
  - Requirements                   |cscope_maps-cscope_maps.nvim-requirements|
  - Features                           |cscope_maps-cscope_maps.nvim-features|
  - Installation                   |cscope_maps-cscope_maps.nvim-installation|
  - Configuration                 |cscope_maps-cscope_maps.nvim-configuration|
  - vim-gutentags                 |cscope_maps-cscope_maps.nvim-vim-gutentags|
  - Keymaps                             |cscope_maps-cscope_maps.nvim-keymaps|
  - Advanced Examples         |cscope_maps-cscope_maps.nvim-advanced-examples|

==============================================================================
1. cscope_maps.nvim                             *cscope_maps-cscope_maps.nvim*

For old school code navigation :)

Heavily inspired by emacs’ xcscope.el <https://github.com/dkogan/xcscope.el>.

**Adds cscope support for Neovim**

cscope_maps.nvim.v2.webm
<https://github.com/dhananjaylatkar/cscope_maps.nvim/assets/27724944/7ab4d902-fe6d-4914-bff6-353136c72803>


REQUIREMENTS                       *cscope_maps-cscope_maps.nvim-requirements*

- Neovim >= 0.10
- cscope <https://sourceforge.net/projects/cscope/files/>


FEATURES                               *cscope_maps-cscope_maps.nvim-features*


CSCOPE ~

- Tries to mimic vim’s builtin cscope functionality.
- Provides user command, `:Cscope` which acts same as good old `:cscope`.
- Short commands are supported. e.g. `:Cs f g <sym>`
- `:Cstag <sym>` does `tags` search if no results are found in `cscope`.
- Empty `<sym>` can be used in `:Cs` and `:Cstag` to pick `<cword>` as sym.
- Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable.
- Keymaps can be disabled using `disable_maps` option.
- `:CsPrompt <op>` can be used to invoke cscope prompt.
- Display results in quickfix list, location list, **telescope**, **fzf-lua**, **mini.pick** or **snacks.nvim**.
- Has which-key.nvim <https://github.com/folke/which-key.nvim> and mini.clue <https://github.com/echasnovski/mini.clue> hints.
- See |cscope_maps-this-section| for `vim-gutentags`.


CSCOPE DB ~

- Add DB files
    - Statically provide table of db paths in config (`db_file`)
    - `db_file` opt can be `function`. This function **must** return table of db paths. This can be used to conditionally (e.g. based on `cwd`) add db paths.
    - Add DB file at runtime using `:Cs db add ...`
        - `:Cs db add <space sepatated files>` add db file(s) to cscope search.
        - `:Cs db rm <space sepatated files>` remove db file(s) from cscope search.
- `:Cs db show` show all db connections.
- `:Cs db build` (re)builds db.
    - If `db_build_cmd.script == "default"` then only primary DB will be built using cscope binary.
        - e.g. `cscope -f ${db_file} ${db_build_cmd.args}` OR `gtags-cscope ${db_build_cmd.args}`
    - Custom script can be provided using `db_build_cmd.script`. Example script is here <https://github.com/dhananjaylatkar/cscope_maps.nvim/pull/67>
        - e.g. user script will be called as following -
        - `${db_build_cmd.script} ${db_build_cmd.args} -d <db1>::<pre_path1> -d <db2>::<pre_path2> ...`
- `vim.g.cscope_maps_statusline_indicator` can be used in statusline to indicate ongoing db build.
- DB path grammar
    - `db_file::db_pre_path` db_pre_path (prefix path) will be appended to cscope results.
    - e.g. `:Cs db add ~/cscope.out::/home/code/proj2` => results from `~/cscope.out` will be prefixed with `/home/code/proj2/`
    - `@` can be used to indicate that parent of `db_file` is `db_pre_path`.
    - e.g. `:Cs db add ../proj2/cscope.out::@` => results from `../proj2/cscope.out` will be prefixed with `../proj2/`


STACK VIEW ~

- Visualize tree of caller functions and called functions.
- `:CsStackView open down <sym>` Opens "downward" stack showing all the functions who call the `<sym>`.
- `:CsStackView open up <sym>` Opens "upward" stack showing all the functions called by the `<sym>`.
- In `CsStackView` window, use following keymaps
    - `<tab>` toggle child under cursor
    - `<cr>` open location of symbol under cursor
    - `<C-v>` open location of symbol under cursor in vertical split
    - `<C-s>` open location of symbol under cursor in horizontal split
    - `q` or `<esc>` close window
    - `<C-u>` or `<C-y>` scroll up preview
    - `<C-d>` or `<C-e>` scroll down preview
- `:CsStackView toggle` reopens last `CsStackView` window.
- In `CsStackView` window, all nodes that are part of current stack are highlighted.


INSTALLATION                       *cscope_maps-cscope_maps.nvim-installation*

Install the plugin with your preferred package manager. Following example uses
lazy.nvim <https://github.com/folke/lazy.nvim>

>lua
    {
      "dhananjaylatkar/cscope_maps.nvim",
      dependencies = {
        "nvim-telescope/telescope.nvim", -- optional [for picker="telescope"]
        "ibhagwan/fzf-lua", -- optional [for picker="fzf-lua"]
        "echasnovski/mini.pick", -- optional [for picker="mini-pick"]
        "folke/snacks.nvim", -- optional [for picker="snacks"]
      },
      opts = {
        -- USE EMPTY FOR DEFAULT OPTIONS
        -- DEFAULTS ARE LISTED BELOW
      },
    }
<


CONFIGURATION                     *cscope_maps-cscope_maps.nvim-configuration*

You must run `require("cscope_maps").setup()` to initialize the plugin even
when using default options.

NOTE: In `vimrc` use `lua require("cscope_maps").setup()`

_cscope_maps_ comes with following defaults:

>lua
    {
      -- maps related defaults
      disable_maps = false, -- "true" disables default keymaps
      skip_input_prompt = false, -- "true" doesn't ask for input
      prefix = "<leader>c", -- prefix to trigger maps
    
      -- cscope related defaults
      cscope = {
        -- location of cscope db file
        db_file = "./cscope.out", -- DB or table of DBs
                                  -- NOTE:
                                  --   when table of DBs is provided -
                                  --   first DB is "primary" and others are "secondary"
                                  --   primary DB is used for build and project_rooter
        -- cscope executable
        exec = "cscope", -- "cscope" or "gtags-cscope"
        -- choose your fav picker
        picker = "quickfix", -- "quickfix", "location", "telescope", "fzf-lua", "mini-pick" or "snacks"
        -- qf_window_size = 5, -- deprecated, replaced by picket_opts below, but still supported for backward compatibility
        -- qf_window_pos = "bottom", -- deprecated, replaced by picket_opts below, but still supported for backward compatibility
        picker_opts = {
          window_size = 5, -- any positive integer
          window_pos = "bottom", -- "bottom", "right", "left" or "top"
          -- options for Snacks picker (---@class snacks.picker.Config)
          -- pass-through options for Snacks picker
          snacks = {}, -- snacks config
        },
        -- "true" does not open picker for single result, just JUMP
        skip_picker_for_single_result = false, -- "false" or "true"
        -- custom script can be used for db build
        db_build_cmd = { script = "default", args = { "-bqkv" } },
        -- statusline indicator, default is cscope executable
        statusline_indicator = nil,
        -- try to locate db_file in parent dir(s)
        project_rooter = {
          enable = false, -- "true" or "false"
          -- change cwd to where db_file is located
          change_cwd = false, -- "true" or "false"
        },
        -- cstag related defaults
        tag = {
          -- bind ":Cstag" to "<C-]>"
          keymap = true, -- "true" or "false"
          -- order of operation to run for ":Cstag"
          order = { "cs", "tag_picker", "tag" }, -- any combination of these 3 (ops can be excluded)
          -- cmd to use for "tag" op in above table
          tag_cmd = "tjump",
        },
      },
    
      -- stack view defaults
      stack_view = {
        tree_hl = true, -- toggle tree highlighting
        size = "medium", -- "medium" or "large" (large is 95% of screen)
      }
    }
<


VIM-GUTENTAGS                     *cscope_maps-cscope_maps.nvim-vim-gutentags*


CONFIG FOR VIM-GUTENTAGS ~

>lua
    {
      "ludovicchabant/vim-gutentags",
      init = function()
        vim.g.gutentags_modules = {"cscope_maps"} -- This is required. Other config is optional
        vim.g.gutentags_cscope_build_inverted_index_maps = 1
        vim.g.gutentags_cache_dir = vim.fn.expand("~/code/.gutentags")
        
        -- ⚠️ WARNING: This limits tags to .c and .h files only, add more extensions (e.g., `-e py`) or remove to avoid skipping other files.
        vim.g.gutentags_file_list_command = "fd -e c -e h"
        
        -- vim.g.gutentags_trace = 1
      end,
    }
<


ALTERNATIVE TO VIM-GUTENTAGS ~

Alternative to gutentags is to rebuild DB using `:Cscope db build` or
`<prefix>b`.

You can create autocmd for running `:Cscope db build` after saving .c and .h
files. e.g

>lua
    local group = vim.api.nvim_create_augroup("CscopeBuild", { clear = true })
    vim.api.nvim_create_autocmd("BufWritePost", {
      pattern = { "*.c", "*.h" },
      callback = function ()
        vim.cmd("Cscope db build")
      end,
      group = group,
    })
<


KEYMAPS                                 *cscope_maps-cscope_maps.nvim-keymaps*


DEFAULT KEYMAPS ~

`<prefix>` can be configured using `prefix` option. Default value for prefix is
`<leader>c`.

(Try setting it to `C-c` )

  -----------------------------------------------------------------------
  Keymaps           Description
  ----------------- -----------------------------------------------------
  <prefix>s         find all references to the token under cursor

  <prefix>g         find global definition(s) of the token under cursor

  <prefix>c         find all calls to the function name under cursor

  <prefix>t         find all instances of the text under cursor

  <prefix>e         egrep search for the word under cursor

  <prefix>f         open the filename under cursor

  <prefix>i         find files that include the filename under cursor

  <prefix>d         find functions that function under cursor calls

  <prefix>a         find places where this symbol is assigned a value

  <prefix>b         build cscope database

  Ctrl-]            do :Cstag <cword>
  -----------------------------------------------------------------------

CUSTOM KEYMAPS ~

Disable default keymaps by setting `disable_maps = true`.

There are 2 ways to add keymaps for `Cscope`.


USING :CSPROMPT COMMAND

`:CsPrompt <op>` is user command to invoke prompt. This command provides prompt
which asks for input before running `:Cscope` command.

e.g. Following snippet maps C-c C-g to find global def of symbol under cursor

>lua
    vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>CsPrompt g<cr>")
<


USING :CSCOPE COMMAND

e.g. Following snippet maps C-c C-g to find global def of symbol under cursor

>lua
    vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>Cs f g<cr>")
<


ADVANCED EXAMPLES             *cscope_maps-cscope_maps.nvim-advanced-examples*


USING SNACKS PICKER WITH CUSTOM OPTIONS ~

Following snippet shows how to use `snacks.nvim` picker with custom layout.

`cscope.picker_opts.snacks` are passed as is to `Snacks.picker()`. More details
about `Snacks.picker()` can be found here
<https://github.com/folke/snacks.nvim/blob/main/docs/picker.md>

The layout used in this example is as follows - - Horizontal layout (input and
list on the left, preview on the right) - Total width is 90% of screen width -
Total height is 85% of screen height - The left side (input and list) takes up
60% of the width - The right side (preview) takes up 40% of the width - Rounded
borders for input and preview windows - Preview window has word wrap enabled

>lua
    
    -- only showing relevant part of the config
    Opts = {
          cscope = {
            picker = 'snacks', -- snacks.picker (alternative: telescope)
            picker_opts = {
              ---@class snacks.picker.Config
              snacks = {
                -- layout = 'vertical', -- Use "vertical" or "horizontal" if you want to use presets
                ---@class snacks.picker.layout.Config
                layout = {
                  layout = {
                    height = 0.85, -- Take up 85% of the total height
                    width = 0.9, -- Take up 90% of the total width (adjust as needed)
                    box = 'horizontal', -- Horizontal layout (input and list on the left, preview on the right)
                    { -- Left side (input and list)
                      box = 'vertical',
                      width = 0.6, -- List and input take up 60% of the width
                      border = 'rounded',
                      { win = 'input', height = 1, border = 'bottom' },
                      { win = 'list', border = 'none' },
                    },
                    { win = 'preview', border = 'rounded', width = 0.4 }, -- Preview window takes up 40% of the width
                  },
                },
                ---@class snacks.picker.win.Config
                win = {
                  preview = {
                    wo = { wrap = true },
                  },
                },
              }, -- snacks
            }, -- picker_opts
          }, -- cscope
        }, -- Opts
<

Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>

vim:tw=78:ts=8:noet:ft=help:norl:


================================================
FILE: lua/cscope/db.lua
================================================
local utils = require("cscope_maps.utils")
local log = require("cscope_maps.utils.log")

local M = {}

--- conns = { {file = db_file, pre_path = db_pre_path}, ... }
M.conns = {}
M.global_conn = nil

M.sep = "::"

M.reset = function()
	M.conns = {}
	M.global_conn = nil
end

---Get all db connections
---If global connection is declared then use that
---@return table
M.all_conns = function()
	M.update_global_conn()
	return M.global_conn or M.conns
end

---Get primary db connection
---If global connection is declared then use that
---@return table
M.primary_conn = function()
	M.update_global_conn()
	if M.global_conn then
		return M.global_conn[1]
	end
	return M.conns[1]
end

---Update primary db connection
---@param file string
---@param pre_path string
M.update_primary_conn = function(file, pre_path)
	M.conns[1].file = vim.fs.normalize(file)
	M.conns[1].pre_path = vim.fs.normalize(pre_path)
end

---Update global db connection
M.update_global_conn = function()
	if vim.g.cscope_maps_db_file then
		local file, pre_path = M.sp_file_pre_path(vim.g.cscope_maps_db_file)
		M.global_conn = { { file = file, pre_path = pre_path } }
	else
		M.global_conn = nil
	end
end

---Split input of ":Cs db add" into file and pre_path and normalize them
---@param path string
---@return string
---@return string|nil
M.sp_file_pre_path = function(path)
	local sp = vim.split(path, M.sep)
	local file = sp[1]
	local pre_path = sp[2]

	file = vim.fs.normalize(file)

	-- use cwd if pre_path is not provided
	if pre_path == nil or pre_path == "" then
		pre_path = vim.fn.getcwd()
	end

	-- use parent as pre_path if its "@"
	if pre_path == "@" then
		pre_path = utils.get_path_parent(file)
	end

	-- normalize it
	pre_path = vim.fs.normalize(pre_path)

	return file, pre_path
end

---Find index of db in all connections
---@param file string
---@param pre_path string|nil
---@return integer
M.find = function(file, pre_path)
	for i, cons in ipairs(M.conns) do
		if
			utils.is_path_same(cons.file, file)
			and (utils.is_path_same(cons.pre_path, pre_path) or cons.pre_path == nil)
		then
			return i
		end
	end

	return -1
end

---Add db in db connections
---@param path string
M.add = function(path)
	local file, pre_path = M.sp_file_pre_path(path)
	if M.find(file, pre_path) == -1 then
		table.insert(M.conns, { file = file, pre_path = pre_path })
	end
end

---Remove db from db connections
---Primary db connection will not be removed
---@param path string
M.remove = function(path)
	local file, pre_path = M.sp_file_pre_path(path)
	local loc = M.find(file, pre_path)
	-- do not remove first entry
	if loc > 1 then
		table.remove(M.conns, loc)
	end
end

---Update DB connections
---@param op string Operation (add/remove)
---@param files table list of files
M.update = function(op, files)
	if op == "a" then
		for _, f in ipairs(files) do
			M.add(f)
		end
	elseif op == "r" then
		for _, f in ipairs(files) do
			M.remove(f)
		end
	end
end

M.print_conns = function()
	if not M.conns then
		log.warn("No connections")
	end

	for i, conn in ipairs(M.conns) do
		local file = utils.get_rel_path(vim.fn.getcwd(), conn.file)
		local pre_path = utils.get_rel_path(vim.fn.getcwd(), conn.pre_path)
		if not pre_path or pre_path == "" then
			log.warn(string.format("%d) db=%s", i, file))
		else
			log.warn(string.format("%d) db=%s pre_path=%s", i, file, pre_path))
		end
	end
end

---Create command to build DB
---1. If script is default then use opt.exec
---2. If custom script is provided then use that with "-d <db>::<pre_path>" args
M.get_build_cmd = function(opts)
	local cmd = {}

	if opts.db_build_cmd.script == "default" then
		if opts.exec == "cscope" then
			cmd = { "cscope", "-f", M.primary_conn().file }
		else -- "gtags-cscope"
			cmd = { "gtags-cscope" }
		end

		vim.list_extend(cmd, opts.db_build_cmd.args)
		return cmd
	end

	-- custom script
	cmd = { opts.db_build_cmd.script }
	vim.list_extend(cmd, opts.db_build_cmd.args)

	for _, conn in ipairs(M.conns) do
		vim.list_extend(cmd, { "-d", string.format("%s::%s", conn.file, conn.pre_path) })
	end

	return cmd
end

local on_exit = function(obj)
	vim.g.cscope_maps_statusline_indicator = nil
	if obj.code == 0 then
		-- print("cscope: [build] out: " .. obj.stdout)
		print("cscope: database built successfully")
	else
		-- print("cscope: [build] out: " .. obj.stderr)
		print("cscope: database build failed")
	end
end

M.build = function(opts)
	if vim.g.cscope_maps_statusline_indicator then
		log.warn("db build is already in progress")
		return
	end

	local cmd = M.get_build_cmd(opts)

	vim.g.cscope_maps_statusline_indicator = opts.statusline_indicator or opts.exec
	vim.system(cmd, { text = true }, on_exit)
end

---Parse db_file and call db.add()
---@param opts table
M.init = function(opts)
	if type(opts.db_file) == "string" then
		M.add(opts.db_file)
		return
	end

	if type(opts.db_file) == "function" then
		opts.db_file = opts.db_file()
	end

	if type(opts.db_file) == "table" then
		for _, f in ipairs(opts.db_file) do
			M.add(f)
		end
	end
end

return M


================================================
FILE: lua/cscope/init.lua
================================================
local RC = require("cscope_maps.utils.ret_codes")
local log = require("cscope_maps.utils.log")
local helper = require("cscope_maps.utils.helper")
local utils = require("cscope_maps.utils")
local db = require("cscope.db")

local M = {}

---@class CsProjectRooterConfig
---@field enable? boolean
---@field change_cwd? boolean

---@class CsPicketOpts
---@field window_size? integer
---@field window_pos? string

---@class CsConfig
---@field db_file? string|[string]
---@field exec? string
---@field picker? string
---@field skip_picker_for_single_result? boolean
---@field db_build_cmd? table
---@field statusline_indicator? string|nil
---@field project_rooter? CsProjectRooterConfig
M.opts = {
	db_file = "./cscope.out",
	exec = "cscope",
	picker = "quickfix",
	picker_opts = {
		window_size = 5,
		window_pos = "bottom",
	},
	skip_picker_for_single_result = false,
	db_build_cmd = { script = "default", args = { "-bqkv" } },
	statusline_indicator = nil,
	project_rooter = {
		enable = false,
		change_cwd = false,
	},
	tag = {
		keymap = true,
		order = { "cs", "tag_picker", "tag" },
		tag_cmd = "tjump",
	},
}

M.user_opts = {}

-- operation symbol to number map
M.op_s_n = {
	s = "0",
	g = "1",
	d = "2",
	c = "3",
	t = "4",
	e = "6",
	f = "7",
	i = "8",
	a = "9",
}

-- operation number to symbol map
M.op_n_s = {}
for k, v in pairs(M.op_s_n) do
	M.op_n_s[v] = k
end

local cscope_picker = nil
local gtags_db = "GTAGS"

M.help = function()
	print([[
Cscope commands:
find : Query for a pattern            (Usage: find a|c|d|e|f|g|i|s|t name)
       a: Find assignments to this symbol
       c: Find functions calling this function
       d: Find functions called by this function
       e: Find this egrep pattern
       f: Find this file
       g: Find this definition
       i: Find files #including this file
       s: Find this C symbol
       t: Find this text string

db   : DB related queries             (Usage: db build|add <files>|rm <files>|show)
       build : Build cscope database
       add   : Add db file(s)
       rm    : Remove db file(s)
       show  : Show current db file(s)

reload: Reload plugin config
help : Show this message              (Usage: help)
]])
end

M.push_tagstack = function()
	local from = { vim.fn.bufnr("%"), vim.fn.line("."), vim.fn.col("."), 0 }
	local items = { { tagname = vim.fn.expand("<cword>"), from = from } }
	local ts = vim.fn.gettagstack()
	local ts_last_item = ts.items[ts.curidx - 1]

	if
		ts_last_item
		and ts_last_item.tagname == items[1].tagname
		and ts_last_item.from[1] == items[1].from[1]
		and ts_last_item.from[2] == items[1].from[2]
	then
		-- Don't push duplicates on tagstack
		return
	end

	vim.fn.settagstack(vim.fn.win_getid(), { items = items }, "t")
end

M.parse_line = function(line, db_pre_path)
	local t = {}

	-- Populate t with filename, context and linenumber
	local sp = vim.split(line, "%s+")

	t.filename = sp[1]
	-- prepend db_pre_path when both of following are true -
	-- 1. relative path is used for filename
	-- 2. db_pre_path is not present in filename
	if db_pre_path and not utils.is_path_abs(t.filename) and not vim.startswith(t.filename, db_pre_path) then
		t.filename = vim.fs.joinpath(db_pre_path, t.filename)
	end
	t.filename = utils.get_rel_path(vim.fn.getcwd(), t.filename)

	t.ctx = sp[2]
	t.lnum = sp[3]
	local sz = #sp[1] + #sp[2] + #sp[3] + 3

	-- Populate t["text"] with search result
	t.text = string.sub(line, sz, -1)

	-- Enclose context with << >>
	if string.sub(t.ctx, 1, 1) == "<" then
		t.ctx = "<" .. t.ctx .. ">"
	else
		t.ctx = "<<" .. t.ctx .. ">>"
	end

	-- Add context to text
	t.text = t.ctx .. t.text

	return t
end

M.parse_output = function(cs_out, db_pre_path)
	-- Parse cscope output to be populated in QuickFix List
	-- setqflist() takes list of dicts to be shown in QF List. See :h setqflist()

	local res = {}

	for line in string.gmatch(cs_out, "([^\n]+)") do
		local parsed_line = M.parse_line(line, db_pre_path)
		table.insert(res, parsed_line)
	end

	return res
end

M.open_picker = function(op_s, symbol, parsed_output)
	local title = "cscope find " .. op_s .. " " .. symbol

	-- Push current symbol on tagstack
	M.push_tagstack()

	-- update jumplist
	vim.cmd([[normal! m']])

	if M.opts.skip_picker_for_single_result and #parsed_output == 1 then
		utils.open_file(parsed_output[1]["filename"], tonumber(parsed_output[1]["lnum"], 10))
		return RC.SUCCESS
	end

	local picker_opts = {}
	picker_opts.cscope = {}
	picker_opts.cscope.parsed_output = parsed_output
	picker_opts.cscope.prompt_title = title
	picker_opts.cscope.picker_opts = M.opts.picker_opts
	-- backward compatibility for qf_window_pos and qf_window_size
	picker_opts.cscope.qf_window_size = M.opts.qf_window_size
	picker_opts.cscope.qf_window_pos = M.opts.qf_window_pos

	cscope_picker.run(picker_opts)
	return RC.SUCCESS
end

M.get_result = function(op_n, op_s, symbol, hide_log)
	-- Executes cscope search and return parsed output

	local db_conns = db.all_conns()
	local res = {}

	local exec_and_update_res = function(_db_con, _cmd_args)
		if vim.loop.fs_stat(_db_con.file) == nil then
			return
		end
		local cmd = {
			M.opts.exec,
			"-dL",
			"-" .. op_n,
			symbol,
			"-f",
			utils.get_rel_path(vim.fn.getcwd(), _db_con.file),
			"-P",
			utils.get_rel_path(vim.fn.getcwd(), _db_con.pre_path),
		}
		cmd = vim.list_extend(cmd, _cmd_args)
		local proc = vim.system(cmd, { text = true }):wait()
		if proc.code ~= 0 then
			return
		end

		res = vim.list_extend(res, M.parse_output(proc.stdout, _db_con.pre_path))
	end

	if M.opts.exec == "cscope" then
		for _, db_con in ipairs(db_conns) do
			exec_and_update_res(db_con, {})
		end
	elseif M.opts.exec == "gtags-cscope" then
		if op_s == "d" then
			log.warn("'d' operation is not available for " .. M.opts.exec, hide_log)
			return RC.INVALID_OP, nil
		end
		exec_and_update_res(db.primary_conn(), { "-a" })
	else
		log.warn("'" .. M.opts.exec .. "' executable is not supported", hide_log)
		return RC.INVALID_EXEC, nil
	end

	if vim.tbl_isempty(res) then
		log.warn("no results for 'cscope find " .. op_s .. " " .. symbol .. "'", hide_log)
		return RC.NO_RESULTS, nil
	end

	return RC.SUCCESS, res
end

M.find = function(op, symbol)
	if symbol == nil then
		return RC.INVALID_SYMBOL
	end

	local ok, res
	op = tostring(op)

	if #op ~= 1 then
		log.warn("operation '" .. op .. "' is invalid")
		return RC.INVALID_OP
	end

	if string.find("012346789", op) then
		ok, res = M.get_result(op, M.op_n_s[op], symbol)
	elseif string.find("sgdctefia", op) then
		ok, res = M.get_result(M.op_s_n[op], op, symbol)
	else
		log.warn("operation '" .. op .. "' is invalid")
		return RC.INVALID_OP
	end

	if ok == RC.SUCCESS then
		return M.open_picker(op, symbol, res)
	end

	return RC.NO_RESULTS
end

-- get lnum and text of tag
-- returns list of all occurrences of tag
M.tag_get_info = function(tag)
	local res = {}
	local bin = ""
	if vim.fn.executable("rg") == 1 then
		bin = "rg"
	elseif vim.fn.executable("grep") == 1 then
		bin = "grep"
	end

	if bin == "" then
		return {}
	end

	-- remove leading and trailing "/" because "cmd" is Ex cmd for vim
	local filter = string.gsub(tag.cmd, "^/", "")
	filter = string.gsub(filter, "/$", "")

	-- escape shell chars
	filter = filter:gsub("[\\~?*|{\\[()%-%.%+]", function(x)
		return "\\" .. x
	end)

	local proc = vim.system({ bin, "-n", filter, tag.filename }, { text = true }):wait()

	if proc.code ~= 0 then
		return {}
	end

	local lines = vim.split(proc.stdout, "\n")
	for _, line in ipairs(lines) do
		local sp = vim.split(line, ":")
		if #sp == 2 then
			table.insert(res, { lnum = sp[1], text = sp[2] })
		end
	end

	return res
end

-- parse taglist for give sym
-- returns same format as cscope parse_output
M.get_tags = function(sym)
	-- don't use custom picker for help tags
	if vim.bo.filetype == "help" then
		return {}
	end

	local tags = vim.fn.taglist(string.format("^%s$", sym))
	local res = {}

	for _, tag in ipairs(tags) do
		local info = M.tag_get_info(tag)
		for _, info_item in ipairs(info) do
			local item = {}
			item.filename = tag.filename
			item.ctx = string.format("<<%s>>", tag.name)
			item.lnum = info_item.lnum
			item.text = string.format("%s %s", item.ctx, info_item.text)
			table.insert(res, item)
		end
	end
	return res
end

M.cstag = function(symbol)
	local op = "g"
	-- if symbol is not provided use cword
	symbol = symbol or M.default_sym(op)

	for _, tag in ipairs(M.opts.tag.order) do
		if tag == "cs" then
			local ok, res = M.get_result(M.op_s_n[op], op, symbol, true)
			if ok == RC.SUCCESS then
				M.open_picker(op, symbol, res)
				return
			end
		elseif tag == "tag_picker" then
			local res = M.get_tags(symbol)
			if #res ~= 0 then
				M.open_picker("tags", symbol, res)
				return
			end
		elseif tag == "tag" then
			local ok, msg = pcall(vim.cmd, { cmd = M.opts.tag.tag_cmd, args = { symbol } })
			if ok then
				return
			end
			log.warn(msg)
		end
	end
end

M.default_sym = function(op)
	local sym = ""
	if vim.fn.mode() == "v" then
		local saved_reg = vim.fn.getreg("v")
		vim.cmd([[noautocmd sil norm! "vy]])
		sym = vim.fn.getreg("v")
		vim.fn.setreg("v", saved_reg)
	else
		local arg = "<cword>"
		if vim.tbl_contains({ "f", "i", "7", "8" }, op) then
			arg = "<cfile>"
		end
		sym = vim.fn.expand(arg)
	end
	return sym
end

M.run = function(args)
	-- Parse top level input and call appropriate functions
	local args_num = #args
	if args_num < 1 then
		-- invalid command
		log.warn("invalid cmd. see :Cscope help")
		return
	end

	local cmd = args[1]

	if cmd:sub(1, 1) == "f" then
		local op = args[2]
		-- if symbol is not provided use cword or cfile
		local symbol = args[3] or M.default_sym(op)

		-- collect all args
		for i = 4, args_num do
			symbol = symbol .. " " .. args[i]
		end

		M.find(op, symbol)
	elseif cmd:sub(1, 1) == "b" then
		log.warn("':Cs build' is deprecated. Use ':Cs db build'")
	elseif cmd:sub(1, 1) == "d" then
		if args_num < 2 then
			log.warn("db command expects atleast 2 arguments")
			return
		end

		local op = args[2]:sub(1, 1)
		if op == "b" then
			db.build(M.opts)
		elseif op == "a" or op == "r" then
			-- collect all args
			local files = {}
			for i = 3, args_num do
				table.insert(files, args[i])
			end

			db.update(op, files)
		elseif op == "s" then
			db.print_conns()
		else
			log.warn("invalid operation")
		end
	elseif cmd:sub(1, 1) == "h" then
		M.help()
	elseif cmd:sub(1, 1) == "r" then
		M.reload()
	else
		log.warn("command '" .. cmd .. "' is invalid")
	end
end

M.cmd_cmp = function(_, line)
	local cmds = { "find", "db", "reload", "help" }
	local l = vim.split(line, "%s+")
	local n = #l - 2

	if n == 0 then
		return vim.tbl_filter(function(val)
			return vim.startswith(val, l[2])
		end, cmds)
	end

	local short_cmd = l[2]:sub(1, 1)
	if n == 1 then
		if short_cmd == "f" then
			return vim.tbl_keys(M.op_s_n)
		end

		if short_cmd == "d" then
			cmds = { "build", "add", "rm", "show" }
			return vim.tbl_filter(function(val)
				return vim.startswith(val, l[3])
			end, cmds)
		end
	end

	local short_cmd2 = l[3]:sub(1, 1)
	local cur_arg = l[#l]

	if n == 2 and short_cmd == "f" then
		-- complete default_sym for "find" cmd
		local default_sym = M.default_sym(short_cmd2)
		if cur_arg == "" or vim.startswith(default_sym, cur_arg) then
			return { default_sym }
		end
	end

	if n >= 2 and short_cmd == "d" and short_cmd2 == "a" then
		local sp = vim.split(cur_arg, db.sep)
		local parent, fs_entries

		if sp[2] ~= nil then
			-- complete pre path.
			-- this will show "@" and dirs only
			parent = utils.get_path_parent(sp[2])
			fs_entries = utils.get_dirs_in_dir(parent)
			table.insert(fs_entries, 1, "@")

			fs_entries = vim.tbl_map(function(x)
				return sp[1] .. db.sep .. x
			end, fs_entries)
		else
			-- complete db path
			-- this will show all files
			parent = utils.get_path_parent(cur_arg)
			fs_entries = utils.get_files_in_dir(parent)
		end

		return vim.tbl_filter(function(val)
			return vim.startswith(val, cur_arg)
		end, fs_entries)
	end

	if n >= 2 and short_cmd == "d" and short_cmd2 == "r" then
		-- complete db_conns except primary_conn
		local db_conns = db.all_conns()
		local entries = {}
		if not db_conns then
			return entries
		end

		for i, conn in ipairs(db_conns) do
			if i > 1 then
				table.insert(entries, string.format("%s%s%s", conn.file, db.sep, conn.pre_path))
			end
		end

		return vim.tbl_filter(function(val)
			return vim.startswith(val, cur_arg)
		end, entries)
	end
end

M.user_command = function()
	-- Create the :Cscope user command
	vim.api.nvim_create_user_command("Cscope", function(opts)
		M.run(opts.fargs)
	end, {
		nargs = "*",
		complete = M.cmd_cmp,
	})

	-- Create the :Cs user command
	vim.api.nvim_create_user_command("Cs", function(opts)
		M.run(opts.fargs)
	end, {
		nargs = "*",
		complete = M.cmd_cmp,
	})

	-- Create the :Cstag user command
	vim.api.nvim_create_user_command("Cstag", function(opts)
		M.cstag(unpack(opts.fargs))
	end, {
		nargs = "*",
	})
	-- Bind :Cstag to "<C-]>"
	if M.opts.tag.keymap == true then
		vim.keymap.set({ "n", "v" }, "<C-]>", "<cmd>Cstag<cr>", { desc = "cstag" })
	end
end

M.reload = function()
	db.reset()
	M.setup(M.user_opts)
end

M.root = function(source, marker)
	if vim.fn.filereadable(vim.fs.joinpath(source, marker)) == 1 then
		return source
	end

	for dir in vim.fs.parents(source) do
		if vim.fn.filereadable(vim.fs.joinpath(dir, marker)) == 1 then
			return dir
		end
	end
	return nil
end

---Initialization api
---@param opts CsConfig
M.setup = function(opts)
	-- save original opts for reload operation
	M.user_opts = vim.deepcopy(opts)
	M.opts = vim.tbl_deep_extend("force", M.opts, opts)
	-- This variable can be used by other plugins to change db_file
	-- e.g. vim-gutentags can use it for when
	--	vim.g.gutentags_cache_dir is enabled.
	vim.g.cscope_maps_db_file = nil
	vim.g.cscope_maps_statusline_indicator = nil

	if M.opts.exec == "gtags-cscope" then
		M.opts.db_file = gtags_db
	end

	db.init(M.opts)

	if M.opts.db_build_cmd.script ~= "default" and vim.fn.executable(M.opts.db_build_cmd.script) ~= 1 then
		log.warn(string.format("db_build script(%s) not found. Using default", M.opts.db_build_cmd.script))
		M.opts.db_build_cmd = { script = "default", args = { "-bqkv" } }
	end

	if M.opts.db_build_cmd_args then
		M.opts.db_build_cmd.args = M.opts.db_build_cmd_args
		log.warn(
			string.format(
				[[db_build_cmd_args is deprecated. Use 'db_build_cmd = { args = %s }']],
				vim.inspect(M.opts.db_build_cmd_args)
			)
		)
	end

	-- if project rooter is enabled,
	-- 1. get root of project and update primary conn
	-- 2. if change_cwd is enabled, change into it (?)
	if M.opts.project_rooter.enable then
		local primary_conn = db.primary_conn()
		local root = M.root(vim.fn.getcwd(), primary_conn.file)
		if root then
			db.update_primary_conn(vim.fs.joinpath(root, primary_conn.file), root)

			if M.opts.project_rooter.change_cwd then
				vim.cmd("cd " .. root)
			end
		end
	end

	cscope_picker = require("cscope.pickers." .. M.opts.picker)
	M.user_command()
end

return M


================================================
FILE: lua/cscope/pickers/fzf-lua.lua
================================================
local config = require("fzf-lua.config")
local make_entry = require("fzf-lua.make_entry")

local M = {}

local prepare = function(parsed_output)
	local res = {}

	for _, entry in ipairs(parsed_output) do
		local _entry = ("%s:%s:%s"):format(
			make_entry.file(entry["filename"], { file_icons = true, color_icons = true }),
			entry["lnum"],
			entry["text"]
		)

		table.insert(res, _entry)
	end
	return res
end

M.run = function(opts)
	local entries = prepare(opts.cscope.parsed_output)
	local _config = { prompt = opts.cscope.prompt_title .. "> " }
	_config = config.normalize_opts(_config, "grep")
	require("fzf-lua").fzf_exec(entries, _config)
end

return M


================================================
FILE: lua/cscope/pickers/location.lua
================================================
local M = {}

M.run = function(opts)
	local pos_cmd = ""

	vim.fn.setloclist(0, opts.cscope.parsed_output)
	vim.fn.setloclist(0, {}, "a", { title = opts.cscope.prompt_title })

	if opts.cscope.picker_opts.window_pos == "top" then
		pos_cmd = "aboveleft"
	elseif opts.cscope.picker_opts.window_pos == "bottom" then
		pos_cmd = "belowright"
	elseif opts.cscope.picker_opts.window_pos == "right" then
		pos_cmd = "belowright vertical"
	elseif opts.cscope.picker_opts.window_pos == "left" then
		pos_cmd = "aboveleft vertical"
	end

	vim.cmd(pos_cmd .. " lopen " .. opts.cscope.picker_opts.window_size)
end

return M


================================================
FILE: lua/cscope/pickers/mini-pick.lua
================================================
local M = {}

M.run = function(opts)
	opts = opts or {}
	local entries = {}

	for _, item in ipairs(opts.cscope.parsed_output) do
		local entry = {
			path = item.filename,
			lnum = tonumber(item.lnum),
			text = string.format("%s:%s:%s", item.filename, item.lnum, item.text),
		}
		table.insert(entries, entry)
	end

	MiniPick.start({
		source = {
			items = entries,
			name = opts.cscope.prompt_title,
			show = function(buf_id, items, query)
				MiniPick.default_show(buf_id, items, query, { show_icons = true })
			end,
		},
	})
end

return M


================================================
FILE: lua/cscope/pickers/quickfix.lua
================================================
local M = {}

M.run = function(opts)
	local pos_cmd = ""
	local window_size = opts.cscope.qf_window_size or opts.cscope.picker_opts.window_size
	local window_pos = opts.cscope.qf_window_pos or opts.cscope.picker_opts.window_pos

	vim.fn.setqflist(opts.cscope.parsed_output)
	vim.fn.setqflist({}, "a", { title = opts.cscope.prompt_title })

	if window_pos == "top" then
		pos_cmd = "topleft"
	elseif window_pos == "bottom" then
		pos_cmd = "botright"
	elseif window_pos == "right" then
		pos_cmd = "botright vertical"
	elseif window_pos == "left" then
		pos_cmd = "topleft vertical"
	end

	vim.cmd(pos_cmd .. " copen " .. window_size)
end

return M


================================================
FILE: lua/cscope/pickers/snacks.lua
================================================
local M = {}

local prepare = function(items)
	local res = {}
	for i, item in ipairs(items) do
		table.insert(res, {
			file = item["filename"],
			score = i,
			text = item["text"],
			line = item["text"],
			pos = { tonumber(item["lnum"]), 0 },
		})
	end

	return res
end

M.run = function(opts)
	local snacks_opts = opts.cscope.picker_opts.snacks or {}
	snacks_opts.items = prepare(opts.cscope.parsed_output)
	snacks_opts.title = opts.cscope.prompt_title
	Snacks.picker(snacks_opts)
end

return M


================================================
FILE: lua/cscope/pickers/telescope.lua
================================================
local M = {}

local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local config = require("telescope.config")
local utils = require("telescope.utils")
local cs_utils = require("cscope_maps.utils")

local entry_maker = function(entry)
	return {
		value = entry,
		display = function()
			local display_filename = cs_utils.get_rel_path(vim.fn.getcwd(), entry["filename"])
			local coordinates = string.format(":%s:", entry["lnum"])
			local display_string = "%s%s%s"
			local display, hl_group, icon = utils.transform_devicons(
				entry["filename"],
				string.format(display_string, display_filename, coordinates, entry["text"]),
				false
			)

			if hl_group then
				return display, { { { 1, #icon }, hl_group } }
			else
				return display
			end
		end,
		ordinal = entry["filename"] .. entry["text"],
		path = cs_utils.get_abs_path(entry["filename"]),
		lnum = tonumber(entry["lnum"]),
	}
end

local finder = nil
local prompt_title = nil

local prepare = function(cscope_parsed_output, telescope_title)
	finder = finders.new_table({
		results = cscope_parsed_output,
		entry_maker = entry_maker,
	})

	prompt_title = telescope_title
end

M.run = function(opts)
	opts = opts or {}
	opts.entry_maker = entry_maker
	prepare(opts.cscope.parsed_output, opts.cscope.prompt_title)

	pickers
		.new(opts, {
			prompt_title = prompt_title,
			finder = finder,
			previewer = config.values.grep_previewer(opts),
			sorter = config.values.generic_sorter(opts),
		})
		:find()
end

return M


================================================
FILE: lua/cscope/stack_view/hl.lua
================================================
local tree = require("cscope.stack_view.tree")
local fn = vim.fn
local api = vim.api

local M = {}
M.ft = "CsStackView"

M.get_pos = function(lnum)
	local line = fn.getline(lnum)
	local indent_len = #line
	line = vim.trim(line)
	indent_len = indent_len - #line

	local line_split = vim.split(line, "%s+")
	local symbol = line_split[2]
	local fname = ""
	local flnum = ""

	if #line_split == 3 then
		local file_loc = vim.split(line_split[3], "::")
		fname = file_loc[1]:sub(2)
		flnum = file_loc[2]:sub(1, -2)
	end

	local indicator_s = indent_len
	local indicator_e = indicator_s + 2

	local symbol_s = indicator_e + 1
	local symbol_e = symbol_s + #symbol

	local bo_s = symbol_e + 1
	local bo_e = bo_s + 1

	local fname_s = symbol_e + 2
	local fname_e = fname_s + #fname

	local delim_s = fname_e
	local delim_e = fname_e + 2

	local lnum_s = fname_e + 2
	local lnum_e = lnum_s + #flnum

	local bc_s = lnum_e
	local bc_e = bc_s + 1

	return indicator_s,
		indicator_e,
		symbol_s,
		symbol_e,
		bo_s,
		bo_e,
		fname_s,
		fname_e,
		delim_s,
		delim_e,
		lnum_s,
		lnum_e,
		bc_s,
		bc_e
end

M.refresh = function(buf, root)
	if vim.bo.filetype ~= M.ft then
		return
	end

	local ns = api.nvim_create_namespace("CsStackViewHighlight")

	local buf_lnum_start = fn.line("w0") - 1
	local buf_lnum_end = fn.line("w$") - 1
	local cursor_lnum = fn.line(".") - 1

	local min_indent = vim.fn.indent(cursor_lnum)

	api.nvim_buf_clear_namespace(buf, ns, 0, -1)

	-- go from cursor up and highlight every place where the indentation gets smaller
	for buf_lnum = cursor_lnum, buf_lnum_start, -1 do
		local index_indent = vim.fn.indent(buf_lnum + 1)
		if buf_lnum == 0 then
			-- always highlight the first line
			api.nvim_buf_add_highlight(buf, ns, "Function", 0, 0, -1)
		elseif (buf_lnum == cursor_lnum) or (index_indent < min_indent) then
			-- highlight the cursor line and all the lines above it whose indentation is smaller than its previous line
			min_indent = index_indent
			local indicator_s, indicator_e, symbol_s, symbol_e, bo_s, bo_e, fname_s, fname_e, delim_s, delim_e, lnum_s, lnum_e, bc_s, bc_e =
				M.get_pos(buf_lnum + 1)
			api.nvim_buf_add_highlight(buf, ns, "Operator", buf_lnum, indicator_s, indicator_e)
			api.nvim_buf_add_highlight(buf, ns, "Function", buf_lnum, symbol_s, symbol_e)
			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, bo_s, bo_e)
			api.nvim_buf_add_highlight(buf, ns, "String", buf_lnum, fname_s, fname_e)
			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, delim_s, delim_e)
			api.nvim_buf_add_highlight(buf, ns, "Number", buf_lnum, lnum_s, lnum_e)
			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, bc_s, bc_e)
		else
			-- all the rest are comments
			api.nvim_buf_add_highlight(buf, ns, "Comment", buf_lnum, 0, -1)
		end
	end

	-- all the lines below the cursor are comments for sure
	for buf_lnum = cursor_lnum + 1, buf_lnum_end do
		api.nvim_buf_add_highlight(buf, ns, "Comment", buf_lnum, 0, -1)
	end
end

return M


================================================
FILE: lua/cscope/stack_view/init.lua
================================================
local cs = require("cscope")
local tree = require("cscope.stack_view.tree")
local hl = require("cscope.stack_view.hl")
local log = require("cscope_maps.utils.log")
local utils = require("cscope_maps.utils")
local M = {}

M.opts = {
	tree_hl = true, -- toggle tree highlighting
	size = "medium", -- "medium" or "large"
}

-- Size presets for stack view window
-- width: fraction of screen width (0.85 = 85% of screen width)
-- height: fraction of screen height (0.7 = 70% of screen height)
local size_presets = {
	medium = { width = 0.85, height = 0.7 },
	large = { width = 0.95, height = 0.95 },
}

-- m()
-- -> a()
-- -> b()
-- -> c()
--
-- a()
-- <- m()
--    <- n()
--
-- {a : {m : { { n : {} }, { o : {} } } }}
-- node = {data: {} children: {}}

-- callers --> DOWN the stack
-- called  --> UP the stack

M.cache = {
	sv = { buf = nil, win = nil },
	pv = { buf = nil, win = nil, files = {}, last_file = "" },
	last_win = nil,
	win_opened = false,
}

M.dir_map = {
	down = {
		indicator = "<- ",
		cs_func = function(symbol)
			local _, res = cs.get_result(cs.op_s_n.c, "c", symbol)
			return res
		end,
	},
	up = {
		indicator = "-> ",
		cs_func = function(symbol)
			local _, res = cs.get_result(cs.op_s_n.d, "d", symbol)
			return res
		end,
	},
}

M.ft = "CsStackView"
local api = vim.api
local fn = vim.fn
local root = nil
local buf_lines = nil
local cur_dir = nil
local buf_last_pos = nil

M.buf_lock = function(buf)
	api.nvim_set_option_value("readonly", true, { buf = buf })
	api.nvim_set_option_value("modifiable", false, { buf = buf })
end

M.buf_unlock = function(buf)
	api.nvim_set_option_value("readonly", false, { buf = buf })
	api.nvim_set_option_value("modifiable", true, { buf = buf })
end

M.pv_scroll = function(dir)
	local input = dir > 0 and [[]] or [[]]

	return function()
		vim.api.nvim_win_call(M.cache.pv.win, function()
			vim.cmd([[normal! ]] .. input)
		end)
	end
end

---Saves last window from where stack_view is opened.
---last_win is used by buf_close() to return to correct window
M.save_last_window = function()
	if M.cache.last_win ~= nil then
		return
	end
	M.cache.last_win = api.nvim_get_current_win()
end

M.set_keymaps = function()
	local opts = { buffer = M.cache.sv.buf, silent = true }

	-- close window
	vim.keymap.set("n", "q", M.toggle_win, opts)
	vim.keymap.set("n", "<esc>", M.toggle_win, opts)

	-- toggle children
	vim.keymap.set("n", "<tab>", M.toggle_children, opts)

	-- open location under cursor
	vim.keymap.set("n", "<cr>", function()
		M.open_action("none")
	end, opts)
	vim.keymap.set("n", "<C-v>", function()
		M.open_action("vert")
	end, opts)
	vim.keymap.set("n", "<C-s>", function()
		M.open_action("horiz")
	end, opts)

	-- scroll up
	vim.keymap.set("n", "<C-u>", M.pv_scroll(-1), opts)
	vim.keymap.set("n", "<C-y>", M.pv_scroll(-1), opts)

	-- scroll down
	vim.keymap.set("n", "<C-d>", M.pv_scroll(1), opts)
	vim.keymap.set("n", "<C-e>", M.pv_scroll(1), opts)
end

M.set_autocmds = function()
	local augroup = api.nvim_create_augroup("CscopeMaps", {})
	api.nvim_create_autocmd({ "BufLeave" }, {
		group = augroup,
		buffer = M.cache.sv.buf,
		callback = M.toggle_win,
	})

	api.nvim_create_autocmd("CursorMoved", {
		group = augroup,
		buffer = M.cache.sv.buf,
		callback = function()
			if M.opts.tree_hl then
				hl.refresh(M.cache.sv.buf, root)
			end
			M.preview_update()
		end,
	})

	api.nvim_create_autocmd("VimResized", {
		group = augroup,
		buffer = M.cache.sv.buf,
		callback = function()
			buf_last_pos = fn.line(".")
			M.buf_close()
			M.buf_open_and_update()
		end,
	})
end

M.buf_open = function()
	local vim_height = vim.o.lines
	local vim_width = vim.o.columns

	-- Each pane takes half the preset width, full preset height.
	-- col/row center the pair of panes on screen.
	local preset = size_presets[M.opts.size]
	local width = math.floor(vim_width * preset.width * 0.5)
	local height = math.floor(vim_height * preset.height)
	local col = vim_width * (1 - preset.width) * 0.5
	local row = vim_height * (1 - preset.height) * 0.5

	M.save_last_window()

	M.cache.pv.buf = M.cache.pv.buf or api.nvim_create_buf(false, true)
	M.cache.pv.win = M.cache.pv.win
		or api.nvim_open_win(M.cache.pv.buf, true, {
			relative = "editor",
			title = "preview",
			title_pos = "center",
			width = width,
			height = height,
			col = col + width + 1,
			row = row,
			style = "minimal",
			focusable = false,
			border = "single",
		})
	api.nvim_set_option_value("filetype", "c", { buf = M.cache.pv.buf })
	api.nvim_set_option_value("cursorline", true, { win = M.cache.pv.win })

	M.cache.sv.buf = M.cache.sv.buf or api.nvim_create_buf(false, true)
	M.cache.sv.win = M.cache.sv.win
		or api.nvim_open_win(M.cache.sv.buf, true, {
			relative = "editor",
			title = M.ft,
			title_pos = "center",
			width = width,
			height = height,
			col = col - 1,
			row = row,
			style = "minimal",
			focusable = false,
			border = "single",
		})
	api.nvim_set_option_value("filetype", M.ft, { buf = M.cache.sv.buf })
	api.nvim_set_option_value("cursorline", true, { win = M.cache.sv.win })

	M.set_keymaps()
	M.set_autocmds()

	M.cache.win_opened = true
end

M.buf_close = function()
	if M.cache.sv.buf ~= nil and api.nvim_buf_is_valid(M.cache.sv.buf) then
		api.nvim_buf_delete(M.cache.sv.buf, { force = true })
	end

	if M.cache.sv.win ~= nil and api.nvim_win_is_valid(M.cache.sv.win) then
		api.nvim_win_close(M.cache.sv.win, true)
	end

	if M.cache.pv.buf ~= nil and api.nvim_buf_is_valid(M.cache.pv.buf) then
		api.nvim_buf_delete(M.cache.pv.buf, { force = true })
	end

	if M.cache.pv.win ~= nil and api.nvim_win_is_valid(M.cache.pv.win) then
		api.nvim_win_close(M.cache.pv.win, true)
	end

	if M.cache.last_win ~= nil and api.nvim_win_is_valid(M.cache.last_win) then
		api.nvim_set_current_win(M.cache.last_win)
	end

	M.cache.sv.buf = nil
	M.cache.sv.win = nil

	M.cache.pv.buf = nil
	M.cache.pv.win = nil

	M.cache.pv.last_file = ""

	M.cache.last_win = nil

	M.cache.win_opened = false
end

---open stack_view window (if not already) and update using buf_lines
M.buf_open_and_update = function()
	if root == nil then
		return
	end

	if not M.cache.win_opened then
		M.buf_open()
	end

	-- print(vim.inspect(root))
	buf_lines = {}
	M.buf_create_lines(root)
	-- print(vim.inspect(buf_lines))

	M.buf_unlock(M.cache.sv.buf)
	api.nvim_buf_set_lines(M.cache.sv.buf, 0, -1, false, buf_lines)
	if buf_last_pos ~= nil then
		api.nvim_win_set_cursor(M.cache.sv.win, { buf_last_pos, 0 })
		buf_last_pos = nil
	end
	M.buf_lock(M.cache.sv.buf)
end

--- Read data from given file
--- @param file string
--- @return table
M.read_lines_from_file = function(file)
	local lines = {}
	for line in io.lines(file) do
		lines[#lines + 1] = line
	end
	return lines
end

--- Update preview window to show location under cursor
M.preview_update = function()
	vim.schedule(function()
		local _, filename, lnum = M.line_to_data(fn.getline("."))
		if filename == "" then
			M.cache.pv.last_file = ""
			api.nvim_buf_set_lines(M.cache.pv.buf, 0, -1, false, {})
			return
		end
		if filename ~= M.cache.pv.last_file then
			local lines = M.cache.pv.files[filename] or M.read_lines_from_file(filename)
			-- cache files for reuse
			M.cache.pv.files[filename] = lines
			M.cache.pv.last_file = filename

			api.nvim_buf_set_lines(M.cache.pv.buf, 0, -1, false, lines)
		end
		api.nvim_win_set_cursor(M.cache.pv.win, { lnum, 0 })
	end)
end

M.line_to_data = function(line)
	line = vim.trim(line)
	local line_split = vim.split(line, "%s+")
	local symbol = line_split[2]
	local filename = ""
	local lnum = 0

	if #line_split == 3 then
		local file_loc = vim.split(line_split[3], "::")
		filename = file_loc[1]:sub(2)
		lnum = tonumber(file_loc[2]:sub(1, -2), 10)
	end

	return symbol, filename, lnum
end

M.buf_create_lines = function(node)
	local item = ""
	if node.is_root then
		item = node.data.symbol
	else
		item = string.format(
			"%s%s%s [%s::%s]",
			string.rep(" ", node.depth * #M.dir_map[cur_dir].indicator),
			M.dir_map[cur_dir].indicator,
			node.data.symbol,
			node.data.filename,
			node.data.lnum
		)
	end

	table.insert(buf_lines, item)
	node.id = #buf_lines

	if not node.children then
		return
	end

	for _, c in ipairs(node.children) do
		M.buf_create_lines(c)
	end
end

M.toggle_children = function()
	if vim.bo.filetype ~= M.ft then
		return
	end

	if cur_dir == nil then
		return
	end

	if root == nil then
		return
	end

	local cur_line = fn.line(".")

	if cur_line == 1 then
		return
	end

	local psymbol, pfilename, plnum = M.line_to_data(fn.getline("."))
	local parent_id = cur_line
	local cs_res = M.dir_map[cur_dir].cs_func(psymbol)

	if not cs_res then
		return
	end

	-- update children list
	local children = {}
	for _, r in ipairs(cs_res) do
		local node = tree.create_node(r.ctx:sub(3, -3), r.filename, r.lnum)
		table.insert(children, node)
	end

	root = tree.update_node(root, parent_id, children)
	M.buf_open_and_update()
end

M.open = function(dir, symbol)
	if vim.bo.filetype == M.ft then
		return
	end

	M.buf_close()
	root = nil
	buf_last_pos = nil

	if not vim.tbl_contains(vim.tbl_keys(M.dir_map), dir) then
		return
	end

	local cs_res = M.dir_map[dir].cs_func(symbol)

	if not cs_res then
		return
	end

	cur_dir = dir

	-- update children list
	local children = {}
	for _, r in ipairs(cs_res) do
		local node = tree.create_node(r.ctx:sub(3, -3), r.filename, r.lnum)
		table.insert(children, node)
	end

	root = tree.create_node(symbol, "", 0)
	root.children = children
	root.is_root = true

	M.buf_open_and_update()
end

M.toggle_win = function()
	if vim.bo.filetype == M.ft then
		buf_last_pos = fn.line(".")
		M.buf_close()
		return
	end
	M.buf_open_and_update()
end

M.open_action = function(split)
	if vim.bo.filetype ~= M.ft then
		return
	end

	if fn.line(".") == 1 then
		return
	end

	local _, pfilename, plnum = M.line_to_data(fn.getline("."))
	M.toggle_win()
	utils.open_file(pfilename, plnum, split)
end

-- :CsStackView toggle
-- :CsStackView open down|up symbol

M.run_cmd = function(args)
	local cmd = args[1]

	if vim.startswith(cmd, "o") then
		local stk_dir = args[2]
		local symbol = args[3] or cs.default_sym("s")
		if vim.startswith(stk_dir, "d") then
			stk_dir = "down"
		elseif vim.startswith(stk_dir, "u") then
			stk_dir = "up"
		end
		M.open(stk_dir, symbol)
	elseif vim.startswith(cmd, "t") then
		M.toggle_win()
	end
end

M.set_user_cmd = function()
	-- Create the :CsStackView user command
	vim.api.nvim_create_user_command("CsStackView", function(opts)
		M.run_cmd(opts.fargs)
	end, {
		nargs = "*",
		complete = function(_, line)
			local cmds = { "open", "toggle" }
			local l = vim.split(line, "%s+")
			local n = #l - 2

			if n == 0 then
				return vim.tbl_filter(function(val)
					return vim.startswith(val, l[2])
				end, cmds)
			end

			if n == 1 and vim.startswith(l[2], "o") then
				return { "down", "up" }
			end
		end,
	})
end

M.setup = function(opts)
	M.opts = vim.tbl_deep_extend("force", M.opts, opts)
	-- Validate size preset (fall back to "medium" if invalid)
	if not size_presets[M.opts.size] then
		log.warn(string.format('Invalid stack_view size "%s", using "medium"', M.opts.size))
		M.opts.size = "medium"
	end
	M.set_user_cmd()
end

return M


================================================
FILE: lua/cscope/stack_view/tree.lua
================================================
local RC = require("cscope_maps.utils.ret_codes")
local M = {}

--- node = {data: d, children: {n1, n2, n3, ...}}

M.create_node = function(symbol, filename, lnum)
	local node = {}

	node.children = nil
	node.depth = 0
	node.data = {}
	node.is_root = false
	node.id = 0

	node.data.symbol = symbol
	node.data.filename = filename
	node.data.lnum = tonumber(lnum, 10)

	return node
end

M.compare_node = function(node, id)
	return (node and node.id == id)
end

M.get_node = function(root, id)
	if M.compare_node(root, id) then
		return root
	end

	local children = root.children

	if not children then
		return nil
	end

	for _, c in ipairs(children) do
		local node = M.get_node(c, id)
		if node ~= nil then
			return node
		end
	end
end

M.update_children_depth = function(children, depth)
	for _, c in ipairs(children) do
		c.depth = depth
	end
end

M.update_children = function(root, parent_id, children)
	local node = M.get_node(root, parent_id)

	if not node then
		return RC.NODE_NOT_FOUND
	end

	if node.children == nil then
		node.children = children
		M.update_children_depth(node.children, node.depth + 1)
	else
		node.children = nil
	end

	return RC.SUCCESS
end

M.update_node = function(root, parent_id, children)
	local ret = M.update_children(root, parent_id, children)

	if ret == RC.SUCCESS then
		return root
	end

	return nil
end

return M


================================================
FILE: lua/cscope_maps/init.lua
================================================
local helper = require("cscope_maps.utils.helper")
local cs = require("cscope")
local M = {}

---@class CsMapsConfig
---@field disable_maps? boolean
---@field skip_input_prompt? boolean
---@field prefix? string
---@field cscope? CsConfig
M.opts = {
	disable_maps = false, -- "true" disables default keymaps
	skip_input_prompt = false, -- "true" doesn't ask for input
	prefix = "<leader>c", -- prefix to trigger maps
	cscope = {}, -- defaults are in cscope.lua
	stack_view = {}, -- defaults are in stack_view
}

-- function to print xcscpoe.el like prompts
M.cscope_prompt = function(operation)
	if
		not vim.tbl_contains(vim.tbl_keys(cs.op_s_n), operation)
		and not vim.tbl_contains(vim.tbl_values(cs.op_s_n), operation)
	then
		return
	end
	if vim.tbl_contains(vim.tbl_values(cs.op_s_n), operation) then
		operation = cs.op_n_s[operation]
	end
	local default_symbol = cs.default_sym(operation)
	if M.opts.skip_input_prompt then
		vim.cmd.Cscope({ args = { "find", operation, default_symbol } })
	else
		local prompt = string.format("%s (default: '%s'): ", helper.sym_map[operation], default_symbol)
		vim.ui.input({ prompt = prompt }, function(new_symbol)
			if new_symbol == nil then
				return
			end
			if new_symbol ~= "" then
				vim.cmd.Cscope({ args = { "find", operation, new_symbol } })
			else
				vim.cmd.Cscope({ args = { "find", operation, default_symbol } })
			end
		end)
	end
end

---Initialization api
---@param opts CsMapsConfig
M.setup = function(opts)
	opts = opts or {}
	M.opts = vim.tbl_deep_extend("force", M.opts, opts)

	vim.api.nvim_create_user_command("CsPrompt", function(opts)
		M.cscope_prompt(opts.fargs[1])
	end, {
		nargs = "*",
		complete = function()
			return vim.tbl_keys(cs.op_s_n)
		end,
	})

	if not M.opts.disable_maps then
		-- Mappings
		helper.default_keymaps(M.opts.prefix)
	end

	cs.setup(M.opts.cscope)
	require("cscope.stack_view").setup(M.opts.stack_view)
end

return M


================================================
FILE: lua/cscope_maps/utils/helper.lua
================================================
local M = {}

-- define key table for input strings
M.sym_map = {
	s = "Find this symbol",
	g = "Find this global definition",
	c = "Find functions calling this function",
	t = "Find this text string",
	e = "Find this egrep pattern",
	f = "Find this file",
	i = "Find files #including this file",
	d = "Find functions called by this function",
	a = "Find places where this symbol is assigned a value",
	b = "Build database",
}

M.default_keymaps = function(prefix)
	local map = vim.keymap.set
	local sym_map = M.sym_map
	if MiniClue then
		table.insert(MiniClue.config.clues, { mode = "n", keys = prefix, desc = "+cscope" })
	else
		local ok, wk = pcall(require, "which-key")
		if ok then
			if wk.add then
				wk.add({ { prefix, group = "+cscope" } })
			else
				wk.register({ [prefix] = { name = "+cscope" } })
			end
		end
	end
	map({ "n", "v" }, prefix .. "s", "<cmd>CsPrompt s<cr>", { desc = sym_map.s })
	map({ "n", "v" }, prefix .. "g", "<cmd>CsPrompt g<cr>", { desc = sym_map.g })
	map({ "n", "v" }, prefix .. "c", "<cmd>CsPrompt c<cr>", { desc = sym_map.c })
	map({ "n", "v" }, prefix .. "t", "<cmd>CsPrompt t<cr>", { desc = sym_map.t })
	map({ "n", "v" }, prefix .. "e", "<cmd>CsPrompt e<cr>", { desc = sym_map.e })
	map({ "n", "v" }, prefix .. "f", "<cmd>CsPrompt f<cr>", { desc = sym_map.f })
	map({ "n", "v" }, prefix .. "i", "<cmd>CsPrompt i<cr>", { desc = sym_map.i })
	map({ "n", "v" }, prefix .. "d", "<cmd>CsPrompt d<cr>", { desc = sym_map.d })
	map({ "n", "v" }, prefix .. "a", "<cmd>CsPrompt a<cr>", { desc = sym_map.a })
	map({ "n", "v" }, prefix .. "b", "<cmd>Cs db build<cr>", { desc = sym_map.b })
end

return M


================================================
FILE: lua/cscope_maps/utils/init.lua
================================================
local M = {}

-- global state of utils
M.g = {
	rel_paths = {},
}

--- Check if given path is absolute path
---@param path string
---@return boolean
M.is_path_abs = function(path)
	if vim.fn.has("nvim-0.11") == 1 then
		return vim.fn.isabsolutepath(path) == 1
	end

	if vim.fn.has("win32") == 1 then
		--  match "\\abc", "C:\abc" or "C:/abc"
		return vim.startswith(path, "\\\\")
			or (path:sub(1, 1):match("%a") and path:sub(2, 2) == ":" and (path:sub(3, 3) == "\\" or path:sub(3, 3) == "/"))
			or false
	end

	return vim.startswith(path, "/") or vim.startswith(path, "~/")
end

--- Get relative path
--- if "rel_to" or "path" are not absolute paths then return "path" as it is
--- else return relative path of "path" wrt to "rel_to"
---@param rel_to string
---@param path string
---@return string
M.get_rel_path = function(rel_to, path)
	if not M.is_path_abs(rel_to) or not M.is_path_abs(path) then
		return path
	end

	-- get memoized path
	local g_key = string.format("%s#%s", rel_to, path)
	if M.g.rel_paths[g_key] then
		return M.g.rel_paths[g_key]
	end

	local rel_path = ""
	local sp_rel_to = vim.split(vim.fs.normalize(rel_to), "/")
	local sp_path = vim.split(vim.fs.normalize(path), "/")
	local len_rel_to = #sp_rel_to + 1
	local len_path = #sp_path + 1
	local i = 1

	-- skip till parents are same
	while i < len_rel_to and i < len_path do
		if sp_rel_to[i] == sp_path[i] then
			i = i + 1
		else
			break
		end
	end

	-- append "../" for remaining parents
	rel_path = rel_path .. string.rep("../", len_rel_to - i)

	-- append remaining path
	rel_path = rel_path .. table.concat(sp_path, "/", i)

	if rel_path == "" then
		rel_path = "."
	end

	-- memoize path
	M.g.rel_paths[g_key] = rel_path

	return rel_path
end

--- Convert given path to absolute path
---@param path string
---@return string
M.get_abs_path = function(path)
	if M.is_path_abs(path) then
		return path
	end

	local abs_path = vim.fs.joinpath(vim.fn.getcwd(), path)

	return vim.fs.normalize(abs_path)
end

--- Get parent of given path
---@param path string
---@return string
M.get_path_parent = function(path)
	for parent in vim.fs.parents(path) do
		return parent
	end
	return ""
end

---Get all dirs and files in given path
---@param dir string
---@return table
M.get_files_in_dir = function(dir)
	local fs_entries = vim.fn.readdir(dir)

	-- add "/" suffix for dirs and return
	return vim.tbl_map(function(x)
		local entry = x
		if dir ~= "." then
			entry = vim.fs.joinpath(dir, x)
		end
		if vim.fn.isdirectory(x) == 1 then
			entry = entry .. "/"
		end
		return entry
	end, fs_entries)
end

---Get all dirs in given path
---@param dir string
---@return table
M.get_dirs_in_dir = function(dir)
	local fs_entries = vim.fn.readdir(dir)

	-- add "/" suffix for dirs
	fs_entries = vim.tbl_map(function(x)
		local entry = x
		if dir ~= "." then
			entry = vim.fs.joinpath(dir, x)
		end
		return entry .. "/"
	end, fs_entries)

	-- return only dirs
	return vim.tbl_filter(function(x)
		return vim.fn.isdirectory(x) == 1
	end, fs_entries)
end

M.is_path_same = function(path1, path2)
	return path1 and path2 and M.get_abs_path(path1) == M.get_abs_path(path2)
end

---Opens file at given line number and split orientation
---@param fname string
---@param lnum number
---@param split string
M.open_file = function(fname, lnum, split)
	if split == "vert" then
		vim.cmd("vsplit")
	elseif split == "horiz" then
		vim.cmd("split")
	end

	if M.is_path_same(vim.api.nvim_buf_get_name(0), fname) then
		-- change position when in same buffer
		vim.api.nvim_win_set_cursor(0, { lnum, 0 })
	else
		vim.cmd(string.format("edit +%d %s", lnum, fname))
	end
end

return M


================================================
FILE: lua/cscope_maps/utils/log.lua
================================================
local M = {}

M.lvl = vim.log.levels
-- DEBUG
-- ERROR
-- INFO
-- TRACE
-- WARN
-- OFF

M.debug = function(msg, hide)
	if hide then
		return
	end
	vim.notify("cscope: " .. msg, M.lvl.DEBUG)
end

M.error = function(msg, hide)
	if hide then
		return
	end
	vim.notify("cscope: " .. msg, M.lvl.ERROR)
end

M.info = function(msg, hide)
	if hide then
		return
	end
	vim.notify("cscope: " .. msg, M.lvl.INFO)
end

M.trace = function(msg, hide)
	if hide then
		return
	end
	vim.notify("cscope: " .. msg, M.lvl.trace)
end

M.warn = function(msg, hide)
	if hide then
		return
	end
	vim.notify("cscope: " .. msg, M.lvl.WARN)
end

return M


================================================
FILE: lua/cscope_maps/utils/ret_codes.lua
================================================
return {
	SUCCESS = 0,
	INVALID_OP = 1,
	INVALID_EXEC = 2,
	DB_NOT_FOUND = 3,
	NO_RESULTS = 4,
	INVALID_SYMBOL = 5,
	NODE_NOT_FOUND = 6,
}
Download .txt
gitextract_qp_pu4jg/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── panvimdoc.yml
├── LICENSE
├── README.md
├── doc/
│   └── cscope_maps.txt
└── lua/
    ├── cscope/
    │   ├── db.lua
    │   ├── init.lua
    │   ├── pickers/
    │   │   ├── fzf-lua.lua
    │   │   ├── location.lua
    │   │   ├── mini-pick.lua
    │   │   ├── quickfix.lua
    │   │   ├── snacks.lua
    │   │   └── telescope.lua
    │   └── stack_view/
    │       ├── hl.lua
    │       ├── init.lua
    │       └── tree.lua
    └── cscope_maps/
        ├── init.lua
        └── utils/
            ├── helper.lua
            ├── init.lua
            ├── log.lua
            └── ret_codes.lua
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (85K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 803,
    "preview": "# These are supported funding model platforms\n\ngithub: dhananjaylatkar\npatreon: # Replace with a single Patreon username"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 709,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: dhananjaylatkar\n\n---\n\n**D"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 608,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: dhananjaylatkar\n\n---\n\n"
  },
  {
    "path": ".github/workflows/panvimdoc.yml",
    "chars": 585,
    "preview": "name: panvimdoc\n\non:\n  push:\n    paths:\n      - \"README.md\"\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n    name: pandoc t"
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2021 Dhananjay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
  },
  {
    "path": "README.md",
    "chars": 11827,
    "preview": "# cscope_maps.nvim\n\nFor old school code navigation :)\n\nHeavily inspired by emacs' [xcscope.el](https://github.com/dkogan"
  },
  {
    "path": "doc/cscope_maps.txt",
    "chars": 13793,
    "preview": "*cscope_maps.txt*\n                             For Neovim >= v0.10.0    Last change: 2026 May 07\n\n======================"
  },
  {
    "path": "lua/cscope/db.lua",
    "chars": 5026,
    "preview": "local utils = require(\"cscope_maps.utils\")\nlocal log = require(\"cscope_maps.utils.log\")\n\nlocal M = {}\n\n--- conns = { {fi"
  },
  {
    "path": "lua/cscope/init.lua",
    "chars": 15037,
    "preview": "local RC = require(\"cscope_maps.utils.ret_codes\")\nlocal log = require(\"cscope_maps.utils.log\")\nlocal helper = require(\"c"
  },
  {
    "path": "lua/cscope/pickers/fzf-lua.lua",
    "chars": 663,
    "preview": "local config = require(\"fzf-lua.config\")\nlocal make_entry = require(\"fzf-lua.make_entry\")\n\nlocal M = {}\n\nlocal prepare ="
  },
  {
    "path": "lua/cscope/pickers/location.lua",
    "chars": 613,
    "preview": "local M = {}\n\nM.run = function(opts)\n\tlocal pos_cmd = \"\"\n\n\tvim.fn.setloclist(0, opts.cscope.parsed_output)\n\tvim.fn.setlo"
  },
  {
    "path": "lua/cscope/pickers/mini-pick.lua",
    "chars": 549,
    "preview": "local M = {}\n\nM.run = function(opts)\n\topts = opts or {}\n\tlocal entries = {}\n\n\tfor _, item in ipairs(opts.cscope.parsed_o"
  },
  {
    "path": "lua/cscope/pickers/quickfix.lua",
    "chars": 648,
    "preview": "local M = {}\n\nM.run = function(opts)\n\tlocal pos_cmd = \"\"\n\tlocal window_size = opts.cscope.qf_window_size or opts.cscope."
  },
  {
    "path": "lua/cscope/pickers/snacks.lua",
    "chars": 500,
    "preview": "local M = {}\n\nlocal prepare = function(items)\n\tlocal res = {}\n\tfor i, item in ipairs(items) do\n\t\ttable.insert(res, {\n\t\t\t"
  },
  {
    "path": "lua/cscope/pickers/telescope.lua",
    "chars": 1521,
    "preview": "local M = {}\n\nlocal pickers = require(\"telescope.pickers\")\nlocal finders = require(\"telescope.finders\")\nlocal config = r"
  },
  {
    "path": "lua/cscope/stack_view/hl.lua",
    "chars": 2990,
    "preview": "local tree = require(\"cscope.stack_view.tree\")\nlocal fn = vim.fn\nlocal api = vim.api\n\nlocal M = {}\nM.ft = \"CsStackView\"\n"
  },
  {
    "path": "lua/cscope/stack_view/init.lua",
    "chars": 11192,
    "preview": "local cs = require(\"cscope\")\nlocal tree = require(\"cscope.stack_view.tree\")\nlocal hl = require(\"cscope.stack_view.hl\")\nl"
  },
  {
    "path": "lua/cscope/stack_view/tree.lua",
    "chars": 1357,
    "preview": "local RC = require(\"cscope_maps.utils.ret_codes\")\nlocal M = {}\n\n--- node = {data: d, children: {n1, n2, n3, ...}}\n\nM.cre"
  },
  {
    "path": "lua/cscope_maps/init.lua",
    "chars": 1921,
    "preview": "local helper = require(\"cscope_maps.utils.helper\")\nlocal cs = require(\"cscope\")\nlocal M = {}\n\n---@class CsMapsConfig\n---"
  },
  {
    "path": "lua/cscope_maps/utils/helper.lua",
    "chars": 1637,
    "preview": "local M = {}\n\n-- define key table for input strings\nM.sym_map = {\n\ts = \"Find this symbol\",\n\tg = \"Find this global defini"
  },
  {
    "path": "lua/cscope_maps/utils/init.lua",
    "chars": 3638,
    "preview": "local M = {}\n\n-- global state of utils\nM.g = {\n\trel_paths = {},\n}\n\n--- Check if given path is absolute path\n---@param pa"
  },
  {
    "path": "lua/cscope_maps/utils/log.lua",
    "chars": 628,
    "preview": "local M = {}\n\nM.lvl = vim.log.levels\n-- DEBUG\n-- ERROR\n-- INFO\n-- TRACE\n-- WARN\n-- OFF\n\nM.debug = function(msg, hide)\n\ti"
  },
  {
    "path": "lua/cscope_maps/utils/ret_codes.lua",
    "chars": 139,
    "preview": "return {\n\tSUCCESS = 0,\n\tINVALID_OP = 1,\n\tINVALID_EXEC = 2,\n\tDB_NOT_FOUND = 3,\n\tNO_RESULTS = 4,\n\tINVALID_SYMBOL = 5,\n\tNOD"
  }
]

About this extraction

This page contains the full source code of the dhananjaylatkar/cscope_maps.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (75.6 KB), approximately 22.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!