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,
}
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.