Add nvim cscope_maps
dblume

dblume commited on 2025-04-03 22:55:25
Showing 18 changed files, with 2333 additions and 0 deletions.


Previously, I installed it manually https://wiki.dlma.com/neovim#cscope
It worked in ~/.config/nvim/pack/cscope_maps/start/cscope_maps.nvim/...
But with a new Ubuntu 24.04 install and nvim v0.11.0, this is the path
(in ~/.local/...) that worked.
... ...
@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2021 Dhananjay
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.
... ...
@@ -0,0 +1,222 @@
1
+# cscope_maps.nvim
2
+
3
+For old school code navigation :)
4
+
5
+Heavily inspired by emacs' [xcscope.el](https://github.com/dkogan/xcscope.el).
6
+
7
+**Adds `cscope` support for [Neovim](https://neovim.io/)**
8
+
9
+[cscope_maps.nvim.v2.webm](https://github.com/dhananjaylatkar/cscope_maps.nvim/assets/27724944/7ab4d902-fe6d-4914-bff6-353136c72803)
10
+
11
+## Requirements
12
+
13
+- Neovim >= 0.10
14
+- [cscope](https://sourceforge.net/projects/cscope/files/)
15
+
16
+## Features
17
+
18
+### Cscope
19
+
20
+- Tries to mimic vim's builtin cscope functionality.
21
+- Provides user command, `:Cscope` which acts same as good old `:cscope`.
22
+- Short commands are supported. e.g. `:Cs f g <sym>`
23
+- `:Cstag <sym>` does `tags` search if no results are found in `cscope`.
24
+- Empty `<sym>` can be used in `:Cs` and `:Cstag` to pick `<cword>` as sym.
25
+- Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable.
26
+- Keymaps can be disabled using `disable_maps` option.
27
+- `:CsPrompt <op>` can be used to invoke cscope prompt.
28
+- Display results in quickfix, **telescope**, **fzf-lua**, **mini.pick** or **snacks.nvim**.
29
+- Has [which-key.nvim](https://github.com/folke/which-key.nvim) and [mini.clue](https://github.com/echasnovski/mini.clue) hints.
30
+- See [this section](#vim-gutentags) for `vim-gutentags`.
31
+
32
+### Cscope DB
33
+
34
+- Statically provide table of db paths in config (`db_file`) OR add them at runtime using `:Cs db add ...`
35
+- `:Cs db add <space sepatated files>` add db file(s) to cscope search.
36
+- `:Cs db rm <space sepatated files>` remove db file(s) from cscope search.
37
+- `:Cs db show` show all db connections.
38
+- `:Cs db build` (re)builds db.
39
+  - If `db_build_cmd.script == "default"` then only primary DB will be built using cscope binary.
40
+    - e.g. `cscope -f ${db_file} ${db_build_cmd.args}` OR `gtags-cscope ${db_build_cmd.args}`
41
+  - Custom script can be provided using `db_build_cmd.script`. Example script is [here](https://github.com/dhananjaylatkar/cscope_maps.nvim/pull/67)
42
+    - e.g. user script will be called as following -
43
+    - `${db_build_cmd.script} ${db_build_cmd.args} -d <db1>::<pre_path1> -d <db2>::<pre_path2> ...`
44
+- `vim.g.cscope_maps_statusline_indicator` can be used in statusline to indicate ongoing db build.
45
+- DB path grammar
46
+  - `db_file::db_pre_path` db_pre_path (prefix path) will be appended to cscope results.
47
+  - e.g. `:Cs db add ~/cscope.out::/home/code/proj2` => results from `~/cscope.out` will be prefixed with `/home/code/proj2/`
48
+  - `@` can be used to indicate that parent of `db_file` is `db_pre_path`.
49
+  - e.g. `:Cs db add ../proj2/cscope.out::@` => results from `../proj2/cscope.out` will be prefixed with `../proj2/`
50
+
51
+### Stack View
52
+
53
+- Visualize tree of caller functions and called functions.
54
+- `:CsStackView open down <sym>` Opens "downward" stack showing all the functions who call the `<sym>`.
55
+- `:CsStackView open up <sym>` Opens "upward" stack showing all the functions called by the `<sym>`.
56
+- In `CsStackView` window, use following keymaps
57
+  - `<tab>` toggle child under cursor
58
+  - `<cr>` open location of symbol under cursor
59
+  - `q` or `<esc>` close window
60
+  - `<C-u>` or `<C-y>` scroll up preview
61
+  - `<C-d>` or `<C-e>` scroll down preview
62
+- `:CsStackView toggle` reopens last `CsStackView` window.
63
+- In `CsStackView` window, all nodes that are part of current stack are highlighted.
64
+
65
+## Installation
66
+
67
+Install the plugin with your preferred package manager.
68
+Following example uses [lazy.nvim](https://github.com/folke/lazy.nvim)
69
+
70
+```lua
71
+{
72
+  "dhananjaylatkar/cscope_maps.nvim",
73
+  dependencies = {
74
+    "nvim-telescope/telescope.nvim", -- optional [for picker="telescope"]
75
+    "ibhagwan/fzf-lua", -- optional [for picker="fzf-lua"]
76
+    "echasnovski/mini.pick", -- optional [for picker="mini-pick"]
77
+    "folke/snacks.nvim", -- optional [for picker="snacks"]
78
+  },
79
+  opts = {
80
+    -- USE EMPTY FOR DEFAULT OPTIONS
81
+    -- DEFAULTS ARE LISTED BELOW
82
+  },
83
+}
84
+```
85
+
86
+## Configuration
87
+
88
+You must run `require("cscope_maps").setup()` to initialize the plugin even when using default options.
89
+
90
+NOTE: In `vimrc` use `lua require("cscope_maps").setup()`
91
+
92
+_cscope_maps_ comes with following defaults:
93
+
94
+```lua
95
+{
96
+  -- maps related defaults
97
+  disable_maps = false, -- "true" disables default keymaps
98
+  skip_input_prompt = false, -- "true" doesn't ask for input
99
+  prefix = "<leader>c", -- prefix to trigger maps
100
+
101
+  -- cscope related defaults
102
+  cscope = {
103
+    -- location of cscope db file
104
+    db_file = "./cscope.out", -- DB or table of DBs
105
+                              -- NOTE:
106
+                              --   when table of DBs is provided -
107
+                              --   first DB is "primary" and others are "secondary"
108
+                              --   primary DB is used for build and project_rooter
109
+    -- cscope executable
110
+    exec = "cscope", -- "cscope" or "gtags-cscope"
111
+    -- choose your fav picker
112
+    picker = "quickfix", -- "quickfix", "telescope", "fzf-lua", "mini-pick" or "snacks"
113
+    -- size of quickfix window
114
+    qf_window_size = 5, -- any positive integer
115
+    -- position of quickfix window
116
+    qf_window_pos = "bottom", -- "bottom", "right", "left" or "top"
117
+    -- "true" does not open picker for single result, just JUMP
118
+    skip_picker_for_single_result = false, -- "false" or "true"
119
+    -- custom script can be used for db build
120
+    db_build_cmd = { script = "default", args = { "-bqkv" } },
121
+    -- statusline indicator, default is cscope executable
122
+    statusline_indicator = nil,
123
+    -- try to locate db_file in parent dir(s)
124
+    project_rooter = {
125
+      enable = false, -- "true" or "false"
126
+      -- change cwd to where db_file is located
127
+      change_cwd = false, -- "true" or "false"
128
+    },
129
+  },
130
+
131
+  -- stack view defaults
132
+  stack_view = {
133
+    tree_hl = true, -- toggle tree highlighting
134
+  }
135
+}
136
+```
137
+
138
+## vim-gutentags
139
+
140
+### Config for vim-gutentags
141
+
142
+```lua
143
+{
144
+  "ludovicchabant/vim-gutentags",
145
+  init = function()
146
+    vim.g.gutentags_modules = {"cscope_maps"} -- This is required. Other config is optional
147
+    vim.g.gutentags_cscope_build_inverted_index_maps = 1
148
+    vim.g.gutentags_cache_dir = vim.fn.expand("~/code/.gutentags")
149
+    vim.g.gutentags_file_list_command = "fd -e c -e h"
150
+    -- vim.g.gutentags_trace = 1
151
+  end,
152
+}
153
+```
154
+
155
+### Alternative to vim-gutentags
156
+
157
+Alternative to gutentags is to rebuild DB using `:Cscope db build` or `<prefix>b`.
158
+
159
+You can create autocmd for running `:Cscope db build` after saving .c and .h files.
160
+e.g
161
+
162
+```lua
163
+local group = vim.api.nvim_create_augroup("CscopeBuild", { clear = true })
164
+vim.api.nvim_create_autocmd("BufWritePost", {
165
+  pattern = { "*.c", "*.h" },
166
+  callback = function ()
167
+    vim.cmd("Cscope db build")
168
+  end,
169
+  group = group,
170
+})
171
+```
172
+
173
+## Keymaps
174
+
175
+### Default Keymaps
176
+
177
+`<prefix>` can be configured using `prefix` option. Default value for prefix
178
+is `<leader>c`.
179
+
180
+(Try setting it to `C-c` 😉)
181
+
182
+| Keymaps           | Description                                         |
183
+| ----------------- | --------------------------------------------------- |
184
+| `<prefix>s`       | find all references to the token under cursor       |
185
+| `<prefix>g`       | find global definition(s) of the token under cursor |
186
+| `<prefix>c`       | find all calls to the function name under cursor    |
187
+| `<prefix>t`       | find all instances of the text under cursor         |
188
+| `<prefix>e`       | egrep search for the word under cursor              |
189
+| `<prefix>f`       | open the filename under cursor                      |
190
+| `<prefix>i`       | find files that include the filename under cursor   |
191
+| `<prefix>d`       | find functions that function under cursor calls     |
192
+| `<prefix>a`       | find places where this symbol is assigned a value   |
193
+| `<prefix>b`       | build cscope database                               |
194
+| <kbd>Ctrl-]</kbd> | do `:Cstag <cword>`                                 |
195
+
196
+### Custom Keymaps
197
+
198
+Disable default keymaps by setting `disable_maps = true`.
199
+
200
+There are 2 ways to add keymaps for `Cscope`.
201
+
202
+#### Using `:CsPrompt` command
203
+
204
+`:CsPrompt <op>` is user command to invoke prompt.
205
+This command provides prompt which asks for input
206
+before running `:Cscope` command.
207
+
208
+e.g. Following snippet maps <kbd>C-c C-g</kbd> to find global def of symbol
209
+under cursor
210
+
211
+```lua
212
+vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>CsPrompt g<cr>")
213
+```
214
+
215
+#### Using `:Cscope` command
216
+
217
+e.g. Following snippet maps <kbd>C-c C-g</kbd> to find global def of symbol
218
+under cursor
219
+
220
+```lua
221
+vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>Cs f g<cr>")
222
+```
... ...
@@ -0,0 +1,266 @@
1
+*cscope_maps.txt*      For Neovim >= v0.10.0     Last change: 2025 February 26
2
+
3
+==============================================================================
4
+Table of Contents                              *cscope_maps-table-of-contents*
5
+
6
+1. cscope_maps.nvim                             |cscope_maps-cscope_maps.nvim|
7
+  - Requirements                   |cscope_maps-cscope_maps.nvim-requirements|
8
+  - Features                           |cscope_maps-cscope_maps.nvim-features|
9
+  - Installation                   |cscope_maps-cscope_maps.nvim-installation|
10
+  - Configuration                 |cscope_maps-cscope_maps.nvim-configuration|
11
+  - vim-gutentags                 |cscope_maps-cscope_maps.nvim-vim-gutentags|
12
+  - Keymaps                             |cscope_maps-cscope_maps.nvim-keymaps|
13
+
14
+==============================================================================
15
+1. cscope_maps.nvim                             *cscope_maps-cscope_maps.nvim*
16
+
17
+For old school code navigation :)
18
+
19
+Heavily inspired by emacs’ xcscope.el <https://github.com/dkogan/xcscope.el>.
20
+
21
+**Adds cscope support for Neovim**
22
+
23
+cscope_maps.nvim.v2.webm
24
+<https://github.com/dhananjaylatkar/cscope_maps.nvim/assets/27724944/7ab4d902-fe6d-4914-bff6-353136c72803>
25
+
26
+
27
+REQUIREMENTS                       *cscope_maps-cscope_maps.nvim-requirements*
28
+
29
+- Neovim >= 0.10
30
+- cscope <https://sourceforge.net/projects/cscope/files/>
31
+
32
+
33
+FEATURES                               *cscope_maps-cscope_maps.nvim-features*
34
+
35
+
36
+CSCOPE ~
37
+
38
+- Tries to mimic vim’s builtin cscope functionality.
39
+- Provides user command, `:Cscope` which acts same as good old `:cscope`.
40
+- Short commands are supported. e.g. `:Cs f g <sym>`
41
+- `:Cstag <sym>` does `tags` search if no results are found in `cscope`.
42
+- Empty `<sym>` can be used in `:Cs` and `:Cstag` to pick `<cword>` as sym.
43
+- Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable.
44
+- Keymaps can be disabled using `disable_maps` option.
45
+- `:CsPrompt <op>` can be used to invoke cscope prompt.
46
+- Display results in quickfix, **telescope**, **fzf-lua**, **mini.pick** or **snacks.nvim**.
47
+- Has which-key.nvim <https://github.com/folke/which-key.nvim> and mini.clue <https://github.com/echasnovski/mini.clue> hints.
48
+- See |cscope_maps-this-section| for `vim-gutentags`.
49
+
50
+
51
+CSCOPE DB ~
52
+
53
+- Statically provide table of db paths in config (`db_file`) OR add them at runtime using `:Cs db add ...`
54
+- `:Cs db add <space sepatated files>` add db file(s) to cscope search.
55
+- `:Cs db rm <space sepatated files>` remove db file(s) from cscope search.
56
+- `:Cs db show` show all db connections.
57
+- `:Cs db build` (re)builds db.
58
+    - If `db_build_cmd.script == "default"` then only primary DB will be built using cscope binary.
59
+        - e.g. `cscope -f ${db_file} ${db_build_cmd.args}` OR `gtags-cscope ${db_build_cmd.args}`
60
+    - Custom script can be provided using `db_build_cmd.script`. Example script is here <https://github.com/dhananjaylatkar/cscope_maps.nvim/pull/67>
61
+        - e.g. user script will be called as following -
62
+        - `${db_build_cmd.script} ${db_build_cmd.args} -d <db1>::<pre_path1> -d <db2>::<pre_path2> ...`
63
+- `vim.g.cscope_maps_statusline_indicator` can be used in statusline to indicate ongoing db build.
64
+- DB path grammar
65
+    - `db_file::db_pre_path` db_pre_path (prefix path) will be appended to cscope results.
66
+    - e.g. `:Cs db add ~/cscope.out::/home/code/proj2` => results from `~/cscope.out` will be prefixed with `/home/code/proj2/`
67
+    - `@` can be used to indicate that parent of `db_file` is `db_pre_path`.
68
+    - e.g. `:Cs db add ../proj2/cscope.out::@` => results from `../proj2/cscope.out` will be prefixed with `../proj2/`
69
+
70
+
71
+STACK VIEW ~
72
+
73
+- Visualize tree of caller functions and called functions.
74
+- `:CsStackView open down <sym>` Opens "downward" stack showing all the functions who call the `<sym>`.
75
+- `:CsStackView open up <sym>` Opens "upward" stack showing all the functions called by the `<sym>`.
76
+- In `CsStackView` window, use following keymaps
77
+    - `<tab>` toggle child under cursor
78
+    - `<cr>` open location of symbol under cursor
79
+    - `q` or `<esc>` close window
80
+    - `<C-u>` or `<C-y>` scroll up preview
81
+    - `<C-d>` or `<C-e>` scroll down preview
82
+- `:CsStackView toggle` reopens last `CsStackView` window.
83
+- In `CsStackView` window, all nodes that are part of current stack are highlighted.
84
+
85
+
86
+INSTALLATION                       *cscope_maps-cscope_maps.nvim-installation*
87
+
88
+Install the plugin with your preferred package manager. Following example uses
89
+lazy.nvim <https://github.com/folke/lazy.nvim>
90
+
91
+>lua
92
+    {
93
+      "dhananjaylatkar/cscope_maps.nvim",
94
+      dependencies = {
95
+        "nvim-telescope/telescope.nvim", -- optional [for picker="telescope"]
96
+        "ibhagwan/fzf-lua", -- optional [for picker="fzf-lua"]
97
+        "echasnovski/mini.pick", -- optional [for picker="mini-pick"]
98
+        "folke/snacks.nvim", -- optional [for picker="snacks"]
99
+      },
100
+      opts = {
101
+        -- USE EMPTY FOR DEFAULT OPTIONS
102
+        -- DEFAULTS ARE LISTED BELOW
103
+      },
104
+    }
105
+<
106
+
107
+
108
+CONFIGURATION                     *cscope_maps-cscope_maps.nvim-configuration*
109
+
110
+You must run `require("cscope_maps").setup()` to initialize the plugin even
111
+when using default options.
112
+
113
+NOTE: In `vimrc` use `lua require("cscope_maps").setup()`
114
+
115
+_cscope_maps_ comes with following defaults:
116
+
117
+>lua
118
+    {
119
+      -- maps related defaults
120
+      disable_maps = false, -- "true" disables default keymaps
121
+      skip_input_prompt = false, -- "true" doesn't ask for input
122
+      prefix = "<leader>c", -- prefix to trigger maps
123
+    
124
+      -- cscope related defaults
125
+      cscope = {
126
+        -- location of cscope db file
127
+        db_file = "./cscope.out", -- DB or table of DBs
128
+                                  -- NOTE:
129
+                                  --   when table of DBs is provided -
130
+                                  --   first DB is "primary" and others are "secondary"
131
+                                  --   primary DB is used for build and project_rooter
132
+        -- cscope executable
133
+        exec = "cscope", -- "cscope" or "gtags-cscope"
134
+        -- choose your fav picker
135
+        picker = "quickfix", -- "quickfix", "telescope", "fzf-lua", "mini-pick" or "snacks"
136
+        -- size of quickfix window
137
+        qf_window_size = 5, -- any positive integer
138
+        -- position of quickfix window
139
+        qf_window_pos = "bottom", -- "bottom", "right", "left" or "top"
140
+        -- "true" does not open picker for single result, just JUMP
141
+        skip_picker_for_single_result = false, -- "false" or "true"
142
+        -- custom script can be used for db build
143
+        db_build_cmd = { script = "default", args = { "-bqkv" } },
144
+        -- statusline indicator, default is cscope executable
145
+        statusline_indicator = nil,
146
+        -- try to locate db_file in parent dir(s)
147
+        project_rooter = {
148
+          enable = false, -- "true" or "false"
149
+          -- change cwd to where db_file is located
150
+          change_cwd = false, -- "true" or "false"
151
+        },
152
+      },
153
+    
154
+      -- stack view defaults
155
+      stack_view = {
156
+        tree_hl = true, -- toggle tree highlighting
157
+      }
158
+    }
159
+<
160
+
161
+
162
+VIM-GUTENTAGS                     *cscope_maps-cscope_maps.nvim-vim-gutentags*
163
+
164
+
165
+CONFIG FOR VIM-GUTENTAGS ~
166
+
167
+>lua
168
+    {
169
+      "ludovicchabant/vim-gutentags",
170
+      init = function()
171
+        vim.g.gutentags_modules = {"cscope_maps"} -- This is required. Other config is optional
172
+        vim.g.gutentags_cscope_build_inverted_index_maps = 1
173
+        vim.g.gutentags_cache_dir = vim.fn.expand("~/code/.gutentags")
174
+        vim.g.gutentags_file_list_command = "fd -e c -e h"
175
+        -- vim.g.gutentags_trace = 1
176
+      end,
177
+    }
178
+<
179
+
180
+
181
+ALTERNATIVE TO VIM-GUTENTAGS ~
182
+
183
+Alternative to gutentags is to rebuild DB using `:Cscope db build` or
184
+`<prefix>b`.
185
+
186
+You can create autocmd for running `:Cscope db build` after saving .c and .h
187
+files. e.g
188
+
189
+>lua
190
+    local group = vim.api.nvim_create_augroup("CscopeBuild", { clear = true })
191
+    vim.api.nvim_create_autocmd("BufWritePost", {
192
+      pattern = { "*.c", "*.h" },
193
+      callback = function ()
194
+        vim.cmd("Cscope db build")
195
+      end,
196
+      group = group,
197
+    })
198
+<
199
+
200
+
201
+KEYMAPS                                 *cscope_maps-cscope_maps.nvim-keymaps*
202
+
203
+
204
+DEFAULT KEYMAPS ~
205
+
206
+`<prefix>` can be configured using `prefix` option. Default value for prefix is
207
+`<leader>c`.
208
+
209
+(Try setting it to `C-c` )
210
+
211
+  -----------------------------------------------------------------------
212
+  Keymaps           Description
213
+  ----------------- -----------------------------------------------------
214
+  <prefix>s         find all references to the token under cursor
215
+
216
+  <prefix>g         find global definition(s) of the token under cursor
217
+
218
+  <prefix>c         find all calls to the function name under cursor
219
+
220
+  <prefix>t         find all instances of the text under cursor
221
+
222
+  <prefix>e         egrep search for the word under cursor
223
+
224
+  <prefix>f         open the filename under cursor
225
+
226
+  <prefix>i         find files that include the filename under cursor
227
+
228
+  <prefix>d         find functions that function under cursor calls
229
+
230
+  <prefix>a         find places where this symbol is assigned a value
231
+
232
+  <prefix>b         build cscope database
233
+
234
+  Ctrl-]            do :Cstag <cword>
235
+  -----------------------------------------------------------------------
236
+
237
+CUSTOM KEYMAPS ~
238
+
239
+Disable default keymaps by setting `disable_maps = true`.
240
+
241
+There are 2 ways to add keymaps for `Cscope`.
242
+
243
+
244
+USING :CSPROMPT COMMAND
245
+
246
+`:CsPrompt <op>` is user command to invoke prompt. This command provides prompt
247
+which asks for input before running `:Cscope` command.
248
+
249
+e.g. Following snippet maps C-c C-g to find global def of symbol under cursor
250
+
251
+>lua
252
+    vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>CsPrompt g<cr>")
253
+<
254
+
255
+
256
+USING :CSCOPE COMMAND
257
+
258
+e.g. Following snippet maps C-c C-g to find global def of symbol under cursor
259
+
260
+>lua
261
+    vim.keymap.set({ "n", "v" }, "<C-c><C-g>", "<cmd>Cs f g<cr>")
262
+<
263
+
264
+Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>
265
+
266
+vim:tw=78:ts=8:noet:ft=help:norl:
... ...
@@ -0,0 +1,201 @@
1
+local utils = require("cscope_maps.utils")
2
+local log = require("cscope_maps.utils.log")
3
+
4
+local M = {}
5
+
6
+--- conns = { {file = db_file, pre_path = db_pre_path}, ... }
7
+M.conns = {}
8
+M.global_conn = nil
9
+
10
+M.sep = "::"
11
+
12
+M.reset = function()
13
+	M.conns = {}
14
+	M.global_conn = nil
15
+end
16
+
17
+---Get all db connections
18
+---If global connection is declared then use that
19
+---@return table
20
+M.all_conns = function()
21
+	M.update_global_conn()
22
+	return M.global_conn or M.conns
23
+end
24
+
25
+---Get primary db connection
26
+---If global connection is declared then use that
27
+---@return table
28
+M.primary_conn = function()
29
+	M.update_global_conn()
30
+	if M.global_conn then
31
+		return M.global_conn[1]
32
+	end
33
+	return M.conns[1]
34
+end
35
+
36
+---Update primary db connection
37
+---@param file string
38
+---@param pre_path string
39
+M.update_primary_conn = function(file, pre_path)
40
+	M.conns[1].file = vim.fs.normalize(file)
41
+	M.conns[1].pre_path = vim.fs.normalize(pre_path)
42
+end
43
+
44
+---Update global db connection
45
+M.update_global_conn = function()
46
+	if vim.g.cscope_maps_db_file then
47
+		local file, pre_path = M.sp_file_pre_path(vim.g.cscope_maps_db_file)
48
+		M.global_conn = { { file = file, pre_path = pre_path } }
49
+	else
50
+		M.global_conn = nil
51
+	end
52
+end
53
+
54
+---Split input of ":Cs db add" into file and pre_path and normalize them
55
+---@param path string
56
+---@return string
57
+---@return string|nil
58
+M.sp_file_pre_path = function(path)
59
+	local sp = vim.split(path, M.sep)
60
+	local file = sp[1]
61
+	local pre_path = sp[2]
62
+
63
+	file = vim.fs.normalize(file)
64
+
65
+	-- use cwd if pre_path is not provided
66
+	if pre_path == nil or pre_path == "" then
67
+		pre_path = vim.fn.getcwd()
68
+	end
69
+
70
+	-- use parent as pre_path if its "@"
71
+	if pre_path == "@" then
72
+		pre_path = utils.get_path_parent(file)
73
+	end
74
+
75
+	-- normalize it
76
+	pre_path = vim.fs.normalize(pre_path)
77
+
78
+	return file, pre_path
79
+end
80
+
81
+---Find index of db in all connections
82
+---@param file string
83
+---@param pre_path string|nil
84
+---@return integer
85
+M.find = function(file, pre_path)
86
+	for i, cons in ipairs(M.conns) do
87
+		if
88
+			utils.is_path_same(cons.file, file)
89
+			and (utils.is_path_same(cons.pre_path, pre_path) or cons.pre_path == nil)
90
+		then
91
+			return i
92
+		end
93
+	end
94
+
95
+	return -1
96
+end
97
+
98
+---Add db in db connections
99
+---@param path string
100
+M.add = function(path)
101
+	local file, pre_path = M.sp_file_pre_path(path)
102
+	if M.find(file, pre_path) == -1 then
103
+		table.insert(M.conns, { file = file, pre_path = pre_path })
104
+	end
105
+end
106
+
107
+---Remove db from db connections
108
+---Primary db connection will not be removed
109
+---@param path string
110
+M.remove = function(path)
111
+	local file, pre_path = M.sp_file_pre_path(path)
112
+	local loc = M.find(file, pre_path)
113
+	-- do not remove first entry
114
+	if loc > 1 then
115
+		table.remove(M.conns, loc)
116
+	end
117
+end
118
+
119
+---Update DB connections
120
+---@param op string Operation (add/remove)
121
+---@param files table list of files
122
+M.update = function(op, files)
123
+	if op == "a" then
124
+		for _, f in ipairs(files) do
125
+			M.add(f)
126
+		end
127
+	elseif op == "r" then
128
+		for _, f in ipairs(files) do
129
+			M.remove(f)
130
+		end
131
+	end
132
+end
133
+
134
+M.print_conns = function()
135
+	if not M.conns then
136
+		log.warn("No connections")
137
+	end
138
+
139
+	for i, conn in ipairs(M.conns) do
140
+		local file = utils.get_rel_path(vim.fn.getcwd(), conn.file)
141
+		local pre_path = utils.get_rel_path(vim.fn.getcwd(), conn.pre_path)
142
+		if not pre_path or pre_path == "" then
143
+			log.warn(string.format("%d) db=%s", i, file))
144
+		else
145
+			log.warn(string.format("%d) db=%s pre_path=%s", i, file, pre_path))
146
+		end
147
+	end
148
+end
149
+
150
+---Create command to build DB
151
+---1. If script is default then use opt.exec
152
+---2. If custom script is provided then use that with "-d <db>::<pre_path>" args
153
+M.get_build_cmd = function(opts)
154
+	local cmd = {}
155
+
156
+	if opts.db_build_cmd.script == "default" then
157
+		if opts.exec == "cscope" then
158
+			cmd = { "cscope", "-f", M.primary_conn().file }
159
+		else -- "gtags-cscope"
160
+			cmd = { "gtags-cscope" }
161
+		end
162
+
163
+		vim.list_extend(cmd, opts.db_build_cmd.args)
164
+		return cmd
165
+	end
166
+
167
+	-- custom script
168
+	cmd = { opts.db_build_cmd.script }
169
+	vim.list_extend(cmd, opts.db_build_cmd.args)
170
+
171
+	for _, conn in ipairs(M.conns) do
172
+		vim.list_extend(cmd, { "-d", string.format("%s::%s", conn.file, conn.pre_path) })
173
+	end
174
+
175
+	return cmd
176
+end
177
+
178
+local on_exit = function(obj)
179
+	vim.g.cscope_maps_statusline_indicator = nil
180
+	if obj.code == 0 then
181
+		-- print("cscope: [build] out: " .. obj.stdout)
182
+		print("cscope: database built successfully")
183
+	else
184
+		-- print("cscope: [build] out: " .. obj.stderr)
185
+		print("cscope: database build failed")
186
+	end
187
+end
188
+
189
+M.build = function(opts)
190
+	if vim.g.cscope_maps_statusline_indicator then
191
+		log.warn("db build is already in progress")
192
+		return
193
+	end
194
+
195
+	local cmd = M.get_build_cmd(opts)
196
+
197
+	vim.g.cscope_maps_statusline_indicator = opts.statusline_indicator or opts.exec
198
+	vim.system(cmd, { text = true }, on_exit)
199
+end
200
+
201
+return M
... ...
@@ -0,0 +1,541 @@
1
+local RC = require("cscope_maps.utils.ret_codes")
2
+local log = require("cscope_maps.utils.log")
3
+local helper = require("cscope_maps.utils.helper")
4
+local utils = require("cscope_maps.utils")
5
+local db = require("cscope.db")
6
+
7
+local M = {}
8
+
9
+---@class CsProjectRooterConfig
10
+---@field enable? boolean
11
+---@field change_cwd? boolean
12
+
13
+---@class CsConfig
14
+---@field db_file? string|[string]
15
+---@field exec? string
16
+---@field picker? string
17
+---@field qf_window_size? integer
18
+---@field qf_window_pos? string
19
+---@field skip_picker_for_single_result? boolean
20
+---@field db_build_cmd? table
21
+---@field statusline_indicator? string|nil
22
+---@field project_rooter? CsProjectRooterConfig
23
+M.opts = {
24
+	db_file = "./cscope.out",
25
+	exec = "cscope",
26
+	picker = "quickfix",
27
+	qf_window_size = 5,
28
+	qf_window_pos = "bottom",
29
+	skip_picker_for_single_result = false,
30
+	db_build_cmd = { script = "default", args = { "-bqkv" } },
31
+	statusline_indicator = nil,
32
+	project_rooter = {
33
+		enable = false,
34
+		change_cwd = false,
35
+	},
36
+}
37
+
38
+M.user_opts = {}
39
+
40
+-- operation symbol to number map
41
+M.op_s_n = {
42
+	s = "0",
43
+	g = "1",
44
+	d = "2",
45
+	c = "3",
46
+	t = "4",
47
+	e = "6",
48
+	f = "7",
49
+	i = "8",
50
+	a = "9",
51
+}
52
+
53
+-- operation number to symbol map
54
+M.op_n_s = {}
55
+for k, v in pairs(M.op_s_n) do
56
+	M.op_n_s[v] = k
57
+end
58
+
59
+local cscope_picker = nil
60
+local gtags_db = "GTAGS"
61
+
62
+M.help = function()
63
+	print([[
64
+Cscope commands:
65
+find : Query for a pattern            (Usage: find a|c|d|e|f|g|i|s|t name)
66
+       a: Find assignments to this symbol
67
+       c: Find functions calling this function
68
+       d: Find functions called by this function
69
+       e: Find this egrep pattern
70
+       f: Find this file
71
+       g: Find this definition
72
+       i: Find files #including this file
73
+       s: Find this C symbol
74
+       t: Find this text string
75
+
76
+db   : DB related queries             (Usage: db build|add <files>|rm <files>|show)
77
+       build : Build cscope database
78
+       add   : Add db file(s)
79
+       rm    : Remove db file(s)
80
+       show  : Show current db file(s)
81
+
82
+reload: Reload plugin config
83
+help : Show this message              (Usage: help)
84
+]])
85
+end
86
+
87
+M.push_tagstack = function()
88
+	local from = { vim.fn.bufnr("%"), vim.fn.line("."), vim.fn.col("."), 0 }
89
+	local items = { { tagname = vim.fn.expand("<cword>"), from = from } }
90
+	local ts = vim.fn.gettagstack()
91
+	local ts_last_item = ts.items[ts.curidx - 1]
92
+
93
+	if
94
+		ts_last_item
95
+		and ts_last_item.tagname == items[1].tagname
96
+		and ts_last_item.from[1] == items[1].from[1]
97
+		and ts_last_item.from[2] == items[1].from[2]
98
+	then
99
+		-- Don't push duplicates on tagstack
100
+		return
101
+	end
102
+
103
+	vim.fn.settagstack(vim.fn.win_getid(), { items = items }, "t")
104
+end
105
+
106
+M.parse_line = function(line, db_pre_path)
107
+	local t = {}
108
+
109
+	-- Populate t with filename, context and linenumber
110
+	local sp = vim.split(line, "%s+")
111
+
112
+	t.filename = sp[1]
113
+	-- prepend db_pre_path when both of following are true -
114
+	-- 1. relative path is used for filename
115
+	-- 2. db_pre_path is not present in filename
116
+	if db_pre_path and not utils.is_path_abs(t.filename) and not vim.startswith(t.filename, db_pre_path) then
117
+		t.filename = vim.fs.joinpath(db_pre_path, t.filename)
118
+	end
119
+	t.filename = utils.get_rel_path(vim.fn.getcwd(), t.filename)
120
+
121
+	t.ctx = sp[2]
122
+	t.lnum = sp[3]
123
+	local sz = #sp[1] + #sp[2] + #sp[3] + 3
124
+
125
+	-- Populate t["text"] with search result
126
+	t.text = string.sub(line, sz, -1)
127
+
128
+	-- Enclose context with << >>
129
+	if string.sub(t.ctx, 1, 1) == "<" then
130
+		t.ctx = "<" .. t.ctx .. ">"
131
+	else
132
+		t.ctx = "<<" .. t.ctx .. ">>"
133
+	end
134
+
135
+	-- Add context to text
136
+	t.text = t.ctx .. t.text
137
+
138
+	return t
139
+end
140
+
141
+M.parse_output = function(cs_out, db_pre_path)
142
+	-- Parse cscope output to be populated in QuickFix List
143
+	-- setqflist() takes list of dicts to be shown in QF List. See :h setqflist()
144
+
145
+	local res = {}
146
+
147
+	for line in string.gmatch(cs_out, "([^\n]+)") do
148
+		local parsed_line = M.parse_line(line, db_pre_path)
149
+		table.insert(res, parsed_line)
150
+	end
151
+
152
+	return res
153
+end
154
+
155
+M.open_picker = function(op_s, symbol, parsed_output)
156
+	local title = "cscope find " .. op_s .. " " .. symbol
157
+
158
+	-- Push current symbol on tagstack
159
+	M.push_tagstack()
160
+
161
+	-- update jumplist
162
+	vim.cmd([[normal! m']])
163
+
164
+	if M.opts.skip_picker_for_single_result and #parsed_output == 1 then
165
+		utils.open_file(parsed_output[1]["filename"], tonumber(parsed_output[1]["lnum"], 10))
166
+		return RC.SUCCESS
167
+	end
168
+
169
+	local picker_opts = {}
170
+	picker_opts.cscope = {}
171
+	picker_opts.cscope.parsed_output = parsed_output
172
+	picker_opts.cscope.prompt_title = title
173
+	picker_opts.cscope.qf_window_size = M.opts.qf_window_size
174
+	picker_opts.cscope.qf_window_pos = M.opts.qf_window_pos
175
+
176
+	cscope_picker.run(picker_opts)
177
+	return RC.SUCCESS
178
+end
179
+
180
+M.cmd_exec = function(cmd)
181
+	local file = assert(io.popen(cmd, "r"))
182
+	file:flush()
183
+	local output = file:read("*all")
184
+	file:close()
185
+	return output
186
+end
187
+
188
+M.get_result = function(op_n, op_s, symbol, hide_log)
189
+	-- Executes cscope search and return parsed output
190
+
191
+	local db_conns = db.all_conns()
192
+	local cmd = string.format("%s -dL -%s %s", M.opts.exec, op_n, symbol)
193
+	local out = ""
194
+	local res = {}
195
+
196
+	local exec_and_update_res = function(_db_con, _cmd_args)
197
+		if vim.loop.fs_stat(_db_con.file) == nil then
198
+			return
199
+		end
200
+
201
+		local _cmd = string.format("%s -f %s -P %s %s", cmd, _db_con.file, _db_con.pre_path, _cmd_args)
202
+		out = M.cmd_exec(_cmd)
203
+		res = vim.list_extend(res, M.parse_output(out, _db_con.pre_path))
204
+	end
205
+
206
+	if M.opts.exec == "cscope" then
207
+		for _, db_con in ipairs(db_conns) do
208
+			exec_and_update_res(db_con, "")
209
+		end
210
+	elseif M.opts.exec == "gtags-cscope" then
211
+		if op_s == "d" then
212
+			log.warn("'d' operation is not available for " .. M.opts.exec, hide_log)
213
+			return RC.INVALID_OP, nil
214
+		end
215
+		exec_and_update_res(db.primary_conn(), "-a")
216
+	else
217
+		log.warn("'" .. M.opts.exec .. "' executable is not supported", hide_log)
218
+		return RC.INVALID_EXEC, nil
219
+	end
220
+
221
+	if vim.tbl_isempty(res) then
222
+		log.warn("no results for 'cscope find " .. op_s .. " " .. symbol .. "'", hide_log)
223
+		return RC.NO_RESULTS, nil
224
+	end
225
+
226
+	return RC.SUCCESS, res
227
+end
228
+
229
+M.find = function(op, symbol)
230
+	if symbol == nil then
231
+		return RC.INVALID_SYMBOL
232
+	end
233
+
234
+	local ok, res
235
+	op = tostring(op)
236
+
237
+	if #op ~= 1 then
238
+		log.warn("operation '" .. op .. "' is invalid")
239
+		return RC.INVALID_OP
240
+	end
241
+
242
+	if string.find("012346789", op) then
243
+		ok, res = M.get_result(op, M.op_n_s[op], symbol)
244
+	elseif string.find("sgdctefia", op) then
245
+		ok, res = M.get_result(M.op_s_n[op], op, symbol)
246
+	else
247
+		log.warn("operation '" .. op .. "' is invalid")
248
+		return RC.INVALID_OP
249
+	end
250
+
251
+	if ok == RC.SUCCESS then
252
+		return M.open_picker(op, symbol, res)
253
+	end
254
+
255
+	return RC.NO_RESULTS
256
+end
257
+
258
+M.cstag = function(symbol)
259
+	local op = "g"
260
+	-- if symbol is not provided use cword
261
+	symbol = symbol or M.default_sym(op)
262
+
263
+	local ok, res = M.get_result(M.op_s_n[op], op, symbol, true)
264
+	if ok == RC.SUCCESS then
265
+		return M.open_picker(op, symbol, res)
266
+	end
267
+	-- log.info("trying tags...")
268
+	if not pcall(vim.cmd.tjump, symbol) then
269
+		log.warn("Vim(tag):E426: tag not found: " .. symbol)
270
+		return RC.NO_RESULTS
271
+	end
272
+	return RC.NO_RESULTS
273
+end
274
+
275
+M.default_sym = function(op)
276
+	local sym = ""
277
+	if vim.fn.mode() == "v" then
278
+		local saved_reg = vim.fn.getreg("v")
279
+		vim.cmd([[noautocmd sil norm! "vy]])
280
+		sym = vim.fn.getreg("v")
281
+		vim.fn.setreg("v", saved_reg)
282
+	else
283
+		local arg = "<cword>"
284
+		if vim.tbl_contains({ "f", "i", "7", "8" }, op) then
285
+			arg = "<cfile>"
286
+		end
287
+		sym = vim.fn.expand(arg)
288
+	end
289
+	return sym
290
+end
291
+
292
+M.run = function(args)
293
+	-- Parse top level input and call appropriate functions
294
+	local args_num = #args
295
+	if args_num < 1 then
296
+		-- invalid command
297
+		log.warn("invalid cmd. see :Cscope help")
298
+		return
299
+	end
300
+
301
+	local cmd = args[1]
302
+
303
+	if cmd:sub(1, 1) == "f" then
304
+		local op = args[2]
305
+		-- if symbol is not provided use cword or cfile
306
+		local symbol = args[3] or M.default_sym(op)
307
+
308
+		-- collect all args
309
+		for i = 4, args_num do
310
+			symbol = symbol .. " " .. args[i]
311
+		end
312
+
313
+		-- escape commonly used special chars
314
+		symbol = symbol:gsub("([%s\"%'%(%)><])", {
315
+			[" "] = "\\ ",
316
+			['"'] = '\\"',
317
+			["'"] = "\\'",
318
+			["("] = "\\(",
319
+			[")"] = "\\)",
320
+			[">"] = "\\>",
321
+			["<"] = "\\<",
322
+		})
323
+
324
+		M.find(op, symbol)
325
+	elseif cmd:sub(1, 1) == "b" then
326
+		log.warn("':Cs build' is deprecated. Use ':Cs db build'")
327
+	elseif cmd:sub(1, 1) == "d" then
328
+		if args_num < 2 then
329
+			log.warn("db command expects atleast 2 arguments")
330
+			return
331
+		end
332
+
333
+		local op = args[2]:sub(1, 1)
334
+		if op == "b" then
335
+			db.build(M.opts)
336
+		elseif op == "a" or op == "r" then
337
+			-- collect all args
338
+			local files = {}
339
+			for i = 3, args_num do
340
+				table.insert(files, args[i])
341
+			end
342
+
343
+			db.update(op, files)
344
+		elseif op == "s" then
345
+			db.print_conns()
346
+		else
347
+			log.warn("invalid operation")
348
+		end
349
+	elseif cmd:sub(1, 1) == "h" then
350
+		M.help()
351
+	elseif cmd:sub(1, 1) == "r" then
352
+		M.reload()
353
+	else
354
+		log.warn("command '" .. cmd .. "' is invalid")
355
+	end
356
+end
357
+
358
+M.cmd_cmp = function(_, line)
359
+	local cmds = { "find", "db", "reload", "help" }
360
+	local l = vim.split(line, "%s+")
361
+	local n = #l - 2
362
+
363
+	if n == 0 then
364
+		return vim.tbl_filter(function(val)
365
+			return vim.startswith(val, l[2])
366
+		end, cmds)
367
+	end
368
+
369
+	local short_cmd = l[2]:sub(1, 1)
370
+	if n == 1 then
371
+		if short_cmd == "f" then
372
+			return vim.tbl_keys(M.op_s_n)
373
+		end
374
+
375
+		if short_cmd == "d" then
376
+			cmds = { "build", "add", "rm", "show" }
377
+			return vim.tbl_filter(function(val)
378
+				return vim.startswith(val, l[3])
379
+			end, cmds)
380
+		end
381
+	end
382
+
383
+	local short_cmd2 = l[3]:sub(1, 1)
384
+	local cur_arg = l[#l]
385
+
386
+	if n == 2 and short_cmd == "f" then
387
+		-- complete default_sym for "find" cmd
388
+		local default_sym = M.default_sym(short_cmd2)
389
+		if cur_arg == "" or vim.startswith(default_sym, cur_arg) then
390
+			return { default_sym }
391
+		end
392
+	end
393
+
394
+	if n >= 2 and short_cmd == "d" and short_cmd2 == "a" then
395
+		local sp = vim.split(cur_arg, db.sep)
396
+		local parent, fs_entries
397
+
398
+		if sp[2] ~= nil then
399
+			-- complete pre path.
400
+			-- this will show "@" and dirs only
401
+			parent = utils.get_path_parent(sp[2])
402
+			fs_entries = utils.get_dirs_in_dir(parent)
403
+			table.insert(fs_entries, 1, "@")
404
+
405
+			fs_entries = vim.tbl_map(function(x)
406
+				return sp[1] .. db.sep .. x
407
+			end, fs_entries)
408
+		else
409
+			-- complete db path
410
+			-- this will show all files
411
+			parent = utils.get_path_parent(cur_arg)
412
+			fs_entries = utils.get_files_in_dir(parent)
413
+		end
414
+
415
+		return vim.tbl_filter(function(val)
416
+			return vim.startswith(val, cur_arg)
417
+		end, fs_entries)
418
+	end
419
+
420
+	if n >= 2 and short_cmd == "d" and short_cmd2 == "r" then
421
+		-- complete db_conns except primary_conn
422
+		local db_conns = db.all_conns()
423
+		local entries = {}
424
+		if not db_conns then
425
+			return entries
426
+		end
427
+
428
+		for i, conn in ipairs(db_conns) do
429
+			if i > 1 then
430
+				table.insert(entries, string.format("%s%s%s", conn.file, db.sep, conn.pre_path))
431
+			end
432
+		end
433
+
434
+		return vim.tbl_filter(function(val)
435
+			return vim.startswith(val, cur_arg)
436
+		end, entries)
437
+	end
438
+end
439
+
440
+M.user_command = function()
441
+	-- Create the :Cscope user command
442
+	vim.api.nvim_create_user_command("Cscope", function(opts)
443
+		M.run(opts.fargs)
444
+	end, {
445
+		nargs = "*",
446
+		complete = M.cmd_cmp,
447
+	})
448
+
449
+	-- Create the :Cs user command
450
+	vim.api.nvim_create_user_command("Cs", function(opts)
451
+		M.run(opts.fargs)
452
+	end, {
453
+		nargs = "*",
454
+		complete = M.cmd_cmp,
455
+	})
456
+
457
+	-- Create the :Cstag user command
458
+	vim.api.nvim_create_user_command("Cstag", function(opts)
459
+		M.cstag(unpack(opts.fargs))
460
+	end, {
461
+		nargs = "*",
462
+	})
463
+end
464
+
465
+M.reload = function()
466
+	db.reset()
467
+	M.setup(M.user_opts)
468
+end
469
+
470
+M.root = function(source, marker)
471
+	if vim.fn.filereadable(vim.fs.joinpath(source, marker)) == 1 then
472
+		return source
473
+	end
474
+
475
+	for dir in vim.fs.parents(source) do
476
+		if vim.fn.filereadable(vim.fs.joinpath(dir, marker)) == 1 then
477
+			return dir
478
+		end
479
+	end
480
+	return nil
481
+end
482
+
483
+---Initialization api
484
+---@param opts CsConfig
485
+M.setup = function(opts)
486
+	-- save original opts for reload operation
487
+	M.user_opts = vim.deepcopy(opts)
488
+	M.opts = vim.tbl_deep_extend("force", M.opts, opts)
489
+	-- This variable can be used by other plugins to change db_file
490
+	-- e.g. vim-gutentags can use it for when
491
+	--	vim.g.gutentags_cache_dir is enabled.
492
+	vim.g.cscope_maps_db_file = nil
493
+	vim.g.cscope_maps_statusline_indicator = nil
494
+
495
+	if M.opts.exec == "gtags-cscope" then
496
+		M.opts.db_file = gtags_db
497
+	end
498
+
499
+	if type(M.opts.db_file) == "string" then
500
+		db.add(M.opts.db_file)
501
+	else -- table
502
+		for _, f in ipairs(M.opts.db_file) do
503
+			db.add(f)
504
+		end
505
+	end
506
+
507
+	if M.opts.db_build_cmd.script ~= "default" and vim.fn.executable(M.opts.db_build_cmd.script) ~= 1 then
508
+		log.warn(string.format("db_build script(%s) not found. Using default", M.opts.db_build_cmd.script))
509
+		M.opts.db_build_cmd = { script = "default", args = { "-bqkv" } }
510
+	end
511
+
512
+	if M.opts.db_build_cmd_args then
513
+		M.opts.db_build_cmd.args = M.opts.db_build_cmd_args
514
+		log.warn(
515
+			string.format(
516
+				[[db_build_cmd_args is deprecated. Use 'db_build_cmd = { args = %s }']],
517
+				vim.inspect(M.opts.db_build_cmd_args)
518
+			)
519
+		)
520
+	end
521
+
522
+	-- if project rooter is enabled,
523
+	-- 1. get root of project and update primary conn
524
+	-- 2. if change_cwd is enabled, change into it (?)
525
+	if M.opts.project_rooter.enable then
526
+		local primary_conn = db.primary_conn()
527
+		local root = M.root(vim.fn.getcwd(), primary_conn.file)
528
+		if root then
529
+			db.update_primary_conn(vim.fs.joinpath(root, primary_conn.file), root)
530
+
531
+			if M.opts.project_rooter.change_cwd then
532
+				vim.cmd("cd " .. root)
533
+			end
534
+		end
535
+	end
536
+
537
+	cscope_picker = require("cscope.pickers." .. M.opts.picker)
538
+	M.user_command()
539
+end
540
+
541
+return M
... ...
@@ -0,0 +1,29 @@
1
+local config = require("fzf-lua.config")
2
+local ansi_codes = require("fzf-lua.utils").ansi_codes
3
+local make_entry = require("fzf-lua.make_entry")
4
+
5
+local M = {}
6
+
7
+local prepare = function(parsed_output)
8
+	local res = {}
9
+
10
+	for _, entry in ipairs(parsed_output) do
11
+		local _entry = ("%s:%s:%s"):format(
12
+			make_entry.file(entry["filename"], { file_icons = true, color_icons = true }),
13
+			ansi_codes.green(entry["lnum"]),
14
+			ansi_codes.magenta(entry["text"])
15
+		)
16
+
17
+		table.insert(res, _entry)
18
+	end
19
+	return res
20
+end
21
+
22
+M.run = function(opts)
23
+	local entries = prepare(opts.cscope.parsed_output)
24
+	local _config = { prompt = opts.cscope.prompt_title .. "> " }
25
+	_config = config.normalize_opts(_config, config.globals.files)
26
+	require("fzf-lua").fzf_exec(entries, _config)
27
+end
28
+
29
+return M
... ...
@@ -0,0 +1,27 @@
1
+local M = {}
2
+
3
+M.run = function(opts)
4
+	opts = opts or {}
5
+	local entries = {}
6
+
7
+	for _, item in ipairs(opts.cscope.parsed_output) do
8
+		local entry = {
9
+			path = item.filename,
10
+			lnum = tonumber(item.lnum),
11
+			text = string.format("%s:%s:%s", item.filename, item.lnum, item.text),
12
+		}
13
+		table.insert(entries, entry)
14
+	end
15
+
16
+	MiniPick.start({
17
+		source = {
18
+			items = entries,
19
+			name = opts.cscope.prompt_title,
20
+			show = function(buf_id, items, query)
21
+				MiniPick.default_show(buf_id, items, query, { show_icons = true })
22
+			end,
23
+		},
24
+	})
25
+end
26
+
27
+return M
... ...
@@ -0,0 +1,22 @@
1
+local M = {}
2
+
3
+M.run = function(opts)
4
+	local pos
5
+
6
+	vim.fn.setqflist(opts.cscope.parsed_output, "r")
7
+	vim.fn.setqflist({}, "a", { title = opts.cscope.prompt_title })
8
+
9
+	if opts.cscope.qf_window_pos == "top" then
10
+		pos = "topleft"
11
+	elseif opts.cscope.qf_window_pos == "bottom" then
12
+		pos = "botright"
13
+	elseif opts.cscope.qf_window_pos == "right" then
14
+		pos = "botright vertical"
15
+	elseif opts.cscope.qf_window_pos == "left" then
16
+		pos = "topleft vertical"
17
+	end
18
+
19
+	vim.cmd(pos .. " copen " .. opts.cscope.qf_window_size)
20
+end
21
+
22
+return M
... ...
@@ -0,0 +1,25 @@
1
+local M = {}
2
+
3
+local prepare = function(items)
4
+	local res = {}
5
+	for i, item in ipairs(items) do
6
+		table.insert(res, {
7
+			file = item["filename"],
8
+			score = i,
9
+			text = item["text"],
10
+			line = item["text"],
11
+			pos = { tonumber(item["lnum"]), 0 },
12
+		})
13
+	end
14
+
15
+	return res
16
+end
17
+
18
+M.run = function(opts)
19
+	Snacks.picker({
20
+		items = prepare(opts.cscope.parsed_output),
21
+		title = opts.cscope.prompt_title,
22
+	})
23
+end
24
+
25
+return M
... ...
@@ -0,0 +1,61 @@
1
+local M = {}
2
+
3
+local pickers = require("telescope.pickers")
4
+local finders = require("telescope.finders")
5
+local config = require("telescope.config")
6
+local utils = require("telescope.utils")
7
+local cs_utils = require("cscope_maps.utils")
8
+
9
+local entry_maker = function(entry)
10
+	return {
11
+		value = entry,
12
+		display = function()
13
+			local display_filename = cs_utils.get_rel_path(vim.fn.getcwd(), entry["filename"])
14
+			local coordinates = string.format(":%s:", entry["lnum"])
15
+			local display_string = "%s%s%s"
16
+			local display, hl_group, icon = utils.transform_devicons(
17
+				entry["filename"],
18
+				string.format(display_string, display_filename, coordinates, entry["text"]),
19
+				false
20
+			)
21
+
22
+			if hl_group then
23
+				return display, { { { 1, #icon }, hl_group } }
24
+			else
25
+				return display
26
+			end
27
+		end,
28
+		ordinal = entry["filename"] .. entry["text"],
29
+		path = cs_utils.get_abs_path(entry["filename"]),
30
+		lnum = tonumber(entry["lnum"]),
31
+	}
32
+end
33
+
34
+local finder = nil
35
+local prompt_title = nil
36
+
37
+local prepare = function(cscope_parsed_output, telescope_title)
38
+	finder = finders.new_table({
39
+		results = cscope_parsed_output,
40
+		entry_maker = entry_maker,
41
+	})
42
+
43
+	prompt_title = telescope_title
44
+end
45
+
46
+M.run = function(opts)
47
+	opts = opts or {}
48
+	opts.entry_maker = entry_maker
49
+	prepare(opts.cscope.parsed_output, opts.cscope.prompt_title)
50
+
51
+	pickers
52
+		.new(opts, {
53
+			prompt_title = prompt_title,
54
+			finder = finder,
55
+			previewer = config.values.grep_previewer(opts),
56
+			sorter = config.values.generic_sorter(opts),
57
+		})
58
+		:find()
59
+end
60
+
61
+return M
... ...
@@ -0,0 +1,96 @@
1
+local tree = require("cscope.stack_view.tree")
2
+local fn = vim.fn
3
+local api = vim.api
4
+
5
+local M = {}
6
+M.ft = "CsStackView"
7
+
8
+M.get_pos = function(lnum)
9
+	local line = fn.getline(lnum)
10
+	local indent_len = #line
11
+	line = vim.trim(line)
12
+	indent_len = indent_len - #line
13
+
14
+	local line_split = vim.split(line, "%s+")
15
+	local symbol = line_split[2]
16
+	local fname = ""
17
+	local flnum = ""
18
+
19
+	if #line_split == 3 then
20
+		local file_loc = vim.split(line_split[3], "::")
21
+		fname = file_loc[1]:sub(2)
22
+		flnum = file_loc[2]:sub(1, -2)
23
+	end
24
+
25
+	local indicator_s = indent_len
26
+	local indicator_e = indicator_s + 2
27
+
28
+	local symbol_s = indicator_e + 1
29
+	local symbol_e = symbol_s + #symbol
30
+
31
+	local bo_s = symbol_e + 1
32
+	local bo_e = bo_s + 1
33
+
34
+	local fname_s = symbol_e + 2
35
+	local fname_e = fname_s + #fname
36
+
37
+	local delim_s = fname_e
38
+	local delim_e = fname_e + 2
39
+
40
+	local lnum_s = fname_e + 2
41
+	local lnum_e = lnum_s + #flnum
42
+
43
+	local bc_s = lnum_e
44
+	local bc_e = bc_s + 1
45
+
46
+	return indicator_s,
47
+		indicator_e,
48
+		symbol_s,
49
+		symbol_e,
50
+		bo_s,
51
+		bo_e,
52
+		fname_s,
53
+		fname_e,
54
+		delim_s,
55
+		delim_e,
56
+		lnum_s,
57
+		lnum_e,
58
+		bc_s,
59
+		bc_e
60
+end
61
+
62
+M.refresh = function(buf, root)
63
+	if vim.bo.filetype ~= M.ft then
64
+		return
65
+	end
66
+	local ancestors = tree.get_ancestors(root, fn.line("."))
67
+
68
+	local ns = api.nvim_create_namespace("CsStackViewHighlight")
69
+
70
+	local buf_lnum_start = fn.line("w0") - 1
71
+	local buf_lnum_end = fn.line("w$")
72
+	local buf_lnum = buf_lnum_start
73
+
74
+	api.nvim_buf_clear_namespace(buf, ns, 0, -1)
75
+
76
+	while buf_lnum < buf_lnum_end do
77
+		if buf_lnum == 0 then
78
+			api.nvim_buf_add_highlight(buf, ns, "Function", buf_lnum, 0, -1)
79
+		elseif vim.tbl_contains(ancestors, buf_lnum + 1) then
80
+			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 =
81
+				M.get_pos(buf_lnum + 1)
82
+			api.nvim_buf_add_highlight(buf, ns, "Operator", buf_lnum, indicator_s, indicator_e)
83
+			api.nvim_buf_add_highlight(buf, ns, "Function", buf_lnum, symbol_s, symbol_e)
84
+			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, bo_s, bo_e)
85
+			api.nvim_buf_add_highlight(buf, ns, "String", buf_lnum, fname_s, fname_e)
86
+			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, delim_s, delim_e)
87
+			api.nvim_buf_add_highlight(buf, ns, "Number", buf_lnum, lnum_s, lnum_e)
88
+			api.nvim_buf_add_highlight(buf, ns, "Delimiter", buf_lnum, bc_s, bc_e)
89
+		else
90
+			api.nvim_buf_add_highlight(buf, ns, "Comment", buf_lnum, 0, -1)
91
+		end
92
+		buf_lnum = buf_lnum + 1
93
+	end
94
+end
95
+
96
+return M
... ...
@@ -0,0 +1,424 @@
1
+local cs = require("cscope")
2
+local tree = require("cscope.stack_view.tree")
3
+local hl = require("cscope.stack_view.hl")
4
+local utils = require("cscope_maps.utils")
5
+local M = {}
6
+
7
+M.opts = {
8
+	tree_hl = true, -- toggle tree highlighting
9
+}
10
+
11
+-- m()
12
+-- -> a()
13
+-- -> b()
14
+-- -> c()
15
+--
16
+-- a()
17
+-- <- m()
18
+--    <- n()
19
+--
20
+-- {a : {m : { { n : {} }, { o : {} } } }}
21
+-- node = {data: {} children: {}}
22
+
23
+-- callers --> DOWN the stack
24
+-- called  --> UP the stack
25
+
26
+M.cache = { sv = { buf = nil, win = nil }, pv = { buf = nil, win = nil, files = {}, last_file = "" } }
27
+M.dir_map = {
28
+	down = {
29
+		indicator = "<- ",
30
+		cs_func = function(symbol)
31
+			local _, res = cs.get_result(cs.op_s_n.c, "c", symbol)
32
+			return res
33
+		end,
34
+	},
35
+	up = {
36
+		indicator = "-> ",
37
+		cs_func = function(symbol)
38
+			local _, res = cs.get_result(cs.op_s_n.d, "d", symbol)
39
+			return res
40
+		end,
41
+	},
42
+}
43
+
44
+M.ft = "CsStackView"
45
+local api = vim.api
46
+local fn = vim.fn
47
+local root = nil
48
+local buf_lines = nil
49
+local cur_dir = nil
50
+local buf_last_pos = nil
51
+
52
+M.buf_lock = function(buf)
53
+	api.nvim_set_option_value("readonly", true, { buf = buf })
54
+	api.nvim_set_option_value("modifiable", false, { buf = buf })
55
+end
56
+
57
+M.buf_unlock = function(buf)
58
+	api.nvim_set_option_value("readonly", false, { buf = buf })
59
+	api.nvim_set_option_value("modifiable", true, { buf = buf })
60
+end
61
+
62
+M.pv_scroll = function(dir)
63
+	local input = dir > 0 and [[]] or [[]]
64
+
65
+	return function()
66
+		vim.api.nvim_win_call(M.cache.pv.win, function()
67
+			vim.cmd([[normal! ]] .. input)
68
+		end)
69
+	end
70
+end
71
+
72
+M.set_keymaps = function()
73
+	local opts = { buffer = M.cache.sv.buf, silent = true }
74
+
75
+	-- close window
76
+	vim.keymap.set("n", "q", M.toggle_win, opts)
77
+	vim.keymap.set("n", "<esc>", M.toggle_win, opts)
78
+
79
+	-- toggle children
80
+	vim.keymap.set("n", "<tab>", M.toggle_children, opts)
81
+
82
+	-- open location under cursor
83
+	vim.keymap.set("n", "<cr>", M.enter_action, opts)
84
+
85
+	-- scroll up
86
+	vim.keymap.set("n", "<C-u>", M.pv_scroll(-1), opts)
87
+	vim.keymap.set("n", "<C-y>", M.pv_scroll(-1), opts)
88
+
89
+	-- scroll down
90
+	vim.keymap.set("n", "<C-d>", M.pv_scroll(1), opts)
91
+	vim.keymap.set("n", "<C-e>", M.pv_scroll(1), opts)
92
+end
93
+
94
+M.buf_open = function()
95
+	local vim_height = vim.o.lines
96
+	local vim_width = vim.o.columns
97
+
98
+	local width = math.floor(vim_width * 0.8 / 2 + 3 / 2)
99
+	local height = math.floor(vim_height * 0.7)
100
+	local col = vim_width * 0.1 - 1
101
+	local row = vim_height * 0.15
102
+
103
+	M.cache.pv.buf = M.cache.pv.buf or api.nvim_create_buf(false, true)
104
+	M.cache.pv.win = M.cache.pv.win
105
+		or api.nvim_open_win(M.cache.pv.buf, true, {
106
+			relative = "editor",
107
+			title = "preview",
108
+			title_pos = "center",
109
+			width = width,
110
+			height = height,
111
+			col = col + 1 + width,
112
+			row = row,
113
+			style = "minimal",
114
+			focusable = false,
115
+			border = "single",
116
+		})
117
+	api.nvim_set_option_value("filetype", "c", { buf = M.cache.pv.buf })
118
+	api.nvim_set_option_value("cursorline", true, { win = M.cache.pv.win })
119
+
120
+	M.cache.sv.buf = M.cache.sv.buf or api.nvim_create_buf(false, true)
121
+	M.cache.sv.win = M.cache.sv.win
122
+		or api.nvim_open_win(M.cache.sv.buf, true, {
123
+			relative = "editor",
124
+			title = M.ft,
125
+			title_pos = "center",
126
+			width = width,
127
+			height = height,
128
+			col = col - 1,
129
+			row = row,
130
+			style = "minimal",
131
+			focusable = false,
132
+			border = "single",
133
+		})
134
+	api.nvim_set_option_value("filetype", M.ft, { buf = M.cache.sv.buf })
135
+	api.nvim_set_option_value("cursorline", true, { win = M.cache.sv.win })
136
+
137
+	M.set_keymaps()
138
+end
139
+
140
+M.buf_close = function()
141
+	if M.cache.sv.buf ~= nil and api.nvim_buf_is_valid(M.cache.sv.buf) then
142
+		api.nvim_buf_delete(M.cache.sv.buf, { force = true })
143
+	end
144
+
145
+	if M.cache.sv.win ~= nil and api.nvim_win_is_valid(M.cache.sv.win) then
146
+		api.nvim_win_close(M.cache.sv.win, true)
147
+	end
148
+
149
+	if M.cache.pv.buf ~= nil and api.nvim_buf_is_valid(M.cache.pv.buf) then
150
+		api.nvim_buf_delete(M.cache.pv.buf, { force = true })
151
+	end
152
+
153
+	if M.cache.pv.win ~= nil and api.nvim_win_is_valid(M.cache.pv.win) then
154
+		api.nvim_win_close(M.cache.pv.win, true)
155
+	end
156
+
157
+	M.cache.sv.buf = nil
158
+	M.cache.sv.win = nil
159
+
160
+	M.cache.pv.buf = nil
161
+	M.cache.pv.win = nil
162
+
163
+	M.cache.pv.last_file = ""
164
+end
165
+
166
+M.buf_update = function()
167
+	if root == nil then
168
+		return
169
+	end
170
+
171
+	-- print(vim.inspect(root))
172
+	buf_lines = {}
173
+	M.buf_create_lines(root)
174
+	-- print(vim.inspect(buf_lines))
175
+	M.buf_open()
176
+
177
+	M.buf_unlock(M.cache.sv.buf)
178
+	api.nvim_buf_set_lines(M.cache.sv.buf, 0, -1, false, buf_lines)
179
+	if buf_last_pos ~= nil then
180
+		api.nvim_win_set_cursor(M.cache.sv.win, { buf_last_pos, 0 })
181
+		buf_last_pos = nil
182
+	end
183
+	M.buf_lock(M.cache.sv.buf)
184
+
185
+	local augroup = api.nvim_create_augroup("CscopeMaps", {})
186
+	api.nvim_create_autocmd({ "BufLeave" }, {
187
+		group = augroup,
188
+		buffer = M.cache.sv.buf,
189
+		callback = M.toggle_win,
190
+	})
191
+
192
+	api.nvim_create_autocmd("CursorMoved", {
193
+		group = augroup,
194
+		buffer = M.cache.sv.buf,
195
+		callback = function()
196
+			if M.opts.tree_hl then
197
+				hl.refresh(M.cache.sv.buf, root)
198
+			end
199
+			M.preview_update()
200
+		end,
201
+	})
202
+end
203
+
204
+--- Read data from given file
205
+--- @param file string
206
+--- @return table
207
+M.read_lines_from_file = function(file)
208
+	local lines = {}
209
+	for line in io.lines(file) do
210
+		lines[#lines + 1] = line
211
+	end
212
+	return lines
213
+end
214
+
215
+--- Update preview window to show location under cursor
216
+M.preview_update = function()
217
+	vim.schedule(function()
218
+		local _, filename, lnum = M.line_to_data(fn.getline("."))
219
+		if filename == "" then
220
+			M.cache.pv.last_file = ""
221
+			api.nvim_buf_set_lines(M.cache.pv.buf, 0, -1, false, {})
222
+			return
223
+		end
224
+		if filename ~= M.cache.pv.last_file then
225
+			local lines = M.cache.pv.files[filename] or M.read_lines_from_file(filename)
226
+			-- cache files for reuse
227
+			M.cache.pv.files[filename] = lines
228
+			M.cache.pv.last_file = filename
229
+
230
+			api.nvim_buf_set_lines(M.cache.pv.buf, 0, -1, false, lines)
231
+		end
232
+		api.nvim_win_set_cursor(M.cache.pv.win, { lnum, 0 })
233
+	end)
234
+end
235
+
236
+M.line_to_data = function(line)
237
+	line = vim.trim(line)
238
+	local line_split = vim.split(line, "%s+")
239
+	local symbol = line_split[2]
240
+	local filename = ""
241
+	local lnum = 0
242
+
243
+	if #line_split == 3 then
244
+		local file_loc = vim.split(line_split[3], "::")
245
+		filename = file_loc[1]:sub(2)
246
+		lnum = tonumber(file_loc[2]:sub(1, -2), 10)
247
+	end
248
+
249
+	return symbol, filename, lnum
250
+end
251
+
252
+M.buf_create_lines = function(node)
253
+	local item = ""
254
+	if node.is_root then
255
+		item = node.data.symbol
256
+	else
257
+		item = string.format(
258
+			"%s%s%s [%s::%s]",
259
+			string.rep(" ", node.depth * #M.dir_map[cur_dir].indicator),
260
+			M.dir_map[cur_dir].indicator,
261
+			node.data.symbol,
262
+			node.data.filename,
263
+			node.data.lnum
264
+		)
265
+	end
266
+
267
+	table.insert(buf_lines, item)
268
+	node.id = #buf_lines
269
+
270
+	if not node.children then
271
+		return
272
+	end
273
+
274
+	for _, c in ipairs(node.children) do
275
+		M.buf_create_lines(c)
276
+	end
277
+end
278
+
279
+M.toggle_children = function()
280
+	if vim.bo.filetype ~= M.ft then
281
+		return
282
+	end
283
+
284
+	if cur_dir == nil then
285
+		return
286
+	end
287
+
288
+	if root == nil then
289
+		return
290
+	end
291
+
292
+	local cur_line = fn.line(".")
293
+
294
+	if cur_line == 1 then
295
+		return
296
+	end
297
+
298
+	local psymbol, pfilename, plnum = M.line_to_data(fn.getline("."))
299
+	local parent_id = cur_line
300
+	local cs_res = M.dir_map[cur_dir].cs_func(psymbol)
301
+
302
+	if not cs_res then
303
+		return
304
+	end
305
+
306
+	-- update children list
307
+	local children = {}
308
+	for _, r in ipairs(cs_res) do
309
+		local node = tree.create_node(r.ctx:sub(3, -3), r.filename, r.lnum)
310
+		table.insert(children, node)
311
+	end
312
+
313
+	root = tree.update_node(root, parent_id, children)
314
+	M.buf_update()
315
+end
316
+
317
+M.open = function(dir, symbol)
318
+	if vim.bo.filetype == M.ft then
319
+		return
320
+	end
321
+
322
+	M.buf_close()
323
+	root = nil
324
+	buf_last_pos = nil
325
+
326
+	if not vim.tbl_contains(vim.tbl_keys(M.dir_map), dir) then
327
+		return
328
+	end
329
+
330
+	local cs_res = M.dir_map[dir].cs_func(symbol)
331
+
332
+	if not cs_res then
333
+		return
334
+	end
335
+
336
+	cur_dir = dir
337
+
338
+	-- update children list
339
+	local children = {}
340
+	for _, r in ipairs(cs_res) do
341
+		local node = tree.create_node(r.ctx:sub(3, -3), r.filename, r.lnum)
342
+		table.insert(children, node)
343
+	end
344
+
345
+	root = tree.create_node(symbol, "", 0)
346
+	root.children = children
347
+	root.is_root = true
348
+
349
+	M.buf_update()
350
+end
351
+
352
+M.toggle_win = function()
353
+	if vim.bo.filetype == M.ft then
354
+		buf_last_pos = fn.line(".")
355
+		M.buf_close()
356
+		return
357
+	end
358
+	M.buf_update()
359
+end
360
+
361
+M.enter_action = function()
362
+	if vim.bo.filetype ~= M.ft then
363
+		return
364
+	end
365
+
366
+	if fn.line(".") == 1 then
367
+		return
368
+	end
369
+
370
+	local _, pfilename, plnum = M.line_to_data(fn.getline("."))
371
+	M.toggle_win()
372
+	utils.open_file(pfilename, plnum)
373
+end
374
+
375
+-- :CsStackView toggle
376
+-- :CsStackView open down|up symbol
377
+
378
+M.run_cmd = function(args)
379
+	local cmd = args[1]
380
+
381
+	if vim.startswith(cmd, "o") then
382
+		local stk_dir = args[2]
383
+		local symbol = args[3] or cs.default_sym("s")
384
+		if vim.startswith(stk_dir, "d") then
385
+			stk_dir = "down"
386
+		elseif vim.startswith(stk_dir, "u") then
387
+			stk_dir = "up"
388
+		end
389
+		M.open(stk_dir, symbol)
390
+	elseif vim.startswith(cmd, "t") then
391
+		M.toggle_win()
392
+	end
393
+end
394
+
395
+M.set_user_cmd = function()
396
+	-- Create the :CsStackView user command
397
+	vim.api.nvim_create_user_command("CsStackView", function(opts)
398
+		M.run_cmd(opts.fargs)
399
+	end, {
400
+		nargs = "*",
401
+		complete = function(_, line)
402
+			local cmds = { "open", "toggle" }
403
+			local l = vim.split(line, "%s+")
404
+			local n = #l - 2
405
+
406
+			if n == 0 then
407
+				return vim.tbl_filter(function(val)
408
+					return vim.startswith(val, l[2])
409
+				end, cmds)
410
+			end
411
+
412
+			if n == 1 and vim.startswith(l[2], "o") then
413
+				return { "down", "up" }
414
+			end
415
+		end,
416
+	})
417
+end
418
+
419
+M.setup = function(opts)
420
+	M.opts = vim.tbl_deep_extend("force", M.opts, opts)
421
+	M.set_user_cmd()
422
+end
423
+
424
+return M
... ...
@@ -0,0 +1,102 @@
1
+local RC = require("cscope_maps.utils.ret_codes")
2
+local M = {}
3
+
4
+--- node = {data: d, children: {n1, n2, n3, ...}}
5
+
6
+M.create_node = function(symbol, filename, lnum)
7
+	local node = {}
8
+
9
+	node.children = nil
10
+	node.depth = 0
11
+	node.data = {}
12
+	node.is_root = false
13
+	node.id = 0
14
+
15
+	node.data.symbol = symbol
16
+	node.data.filename = filename
17
+	node.data.lnum = tonumber(lnum, 10)
18
+
19
+	return node
20
+end
21
+
22
+M.compare_node = function(node, id)
23
+	return (node and node.id == id)
24
+end
25
+
26
+M.get_node = function(root, id)
27
+	if M.compare_node(root, id) then
28
+		return root
29
+	end
30
+
31
+	local children = root.children
32
+
33
+	if not children then
34
+		return nil
35
+	end
36
+
37
+	for _, c in ipairs(children) do
38
+		local node = M.get_node(c, id)
39
+		if node ~= nil then
40
+			return node
41
+		end
42
+	end
43
+end
44
+
45
+M.update_children_depth = function(children, depth)
46
+	for _, c in ipairs(children) do
47
+		c.depth = depth
48
+	end
49
+end
50
+
51
+M.update_children = function(root, parent_id, children)
52
+	local node = M.get_node(root, parent_id)
53
+
54
+	if not node then
55
+		return RC.NODE_NOT_FOUND
56
+	end
57
+
58
+	if node.children == nil then
59
+		node.children = children
60
+		M.update_children_depth(node.children, node.depth + 1)
61
+	else
62
+		node.children = nil
63
+	end
64
+
65
+	return RC.SUCCESS
66
+end
67
+
68
+M.update_node = function(root, parent_id, children)
69
+	local ret = M.update_children(root, parent_id, children)
70
+
71
+	if ret == RC.SUCCESS then
72
+		return root
73
+	end
74
+
75
+	return nil
76
+end
77
+
78
+M.get_ancestors = function(root, node_id)
79
+	if root.id == node_id then
80
+		return { root.id }
81
+	end
82
+
83
+	local st = { { root } }
84
+	while #st ~= 0 do
85
+		local cur_path = table.remove(st, 1)
86
+		local cur_node = cur_path[#cur_path]
87
+		if cur_node.children then
88
+			for _, c in ipairs(cur_node.children) do
89
+				table.insert(cur_path, c)
90
+				if c.id == node_id then
91
+					return vim.tbl_map(function(x)
92
+						return x.id
93
+					end, cur_path)
94
+				end
95
+				table.insert(st, vim.deepcopy(cur_path))
96
+				table.remove(cur_path)
97
+			end
98
+		end
99
+	end
100
+end
101
+
102
+return M
... ...
@@ -0,0 +1,71 @@
1
+local helper = require("cscope_maps.utils.helper")
2
+local cs = require("cscope")
3
+local M = {}
4
+
5
+---@class CsMapsConfig
6
+---@field disable_maps? boolean
7
+---@field skip_input_prompt? boolean
8
+---@field prefix? string
9
+---@field cscope? CsConfig
10
+M.opts = {
11
+	disable_maps = false, -- "true" disables default keymaps
12
+	skip_input_prompt = false, -- "true" doesn't ask for input
13
+	prefix = "<leader>c", -- prefix to trigger maps
14
+	cscope = {}, -- defaults are in cscope.lua
15
+	stack_view = {}, -- defaults are in stack_view
16
+}
17
+
18
+-- function to print xcscpoe.el like prompts
19
+M.cscope_prompt = function(operation)
20
+	if
21
+		not vim.tbl_contains(vim.tbl_keys(cs.op_s_n), operation)
22
+		and not vim.tbl_contains(vim.tbl_values(cs.op_s_n), operation)
23
+	then
24
+		return
25
+	end
26
+	if vim.tbl_contains(vim.tbl_values(cs.op_s_n), operation) then
27
+		operation = cs.op_n_s[operation]
28
+	end
29
+	local default_symbol = cs.default_sym(operation)
30
+	if M.opts.skip_input_prompt then
31
+		vim.cmd.Cscope({ args = { "find", operation, default_symbol } })
32
+	else
33
+		local prompt = string.format("%s (default: '%s'): ", helper.sym_map[operation], default_symbol)
34
+		vim.ui.input({ prompt = prompt }, function(new_symbol)
35
+			if new_symbol == nil then
36
+				return
37
+			end
38
+			if new_symbol ~= "" then
39
+				vim.cmd.Cscope({ args = { "find", operation, new_symbol } })
40
+			else
41
+				vim.cmd.Cscope({ args = { "find", operation, default_symbol } })
42
+			end
43
+		end)
44
+	end
45
+end
46
+
47
+---Initialization api
48
+---@param opts CsMapsConfig
49
+M.setup = function(opts)
50
+	opts = opts or {}
51
+	M.opts = vim.tbl_deep_extend("force", M.opts, opts)
52
+
53
+	vim.api.nvim_create_user_command("CsPrompt", function(opts)
54
+		M.cscope_prompt(opts.fargs[1])
55
+	end, {
56
+		nargs = "*",
57
+		complete = function()
58
+			return vim.tbl_keys(cs.op_s_n)
59
+		end,
60
+	})
61
+
62
+	if not M.opts.disable_maps then
63
+		-- Mappings
64
+		helper.default_keymaps(M.opts.prefix)
65
+	end
66
+
67
+	cs.setup(M.opts.cscope)
68
+	require("cscope.stack_view").setup(M.opts.stack_view)
69
+end
70
+
71
+return M
... ...
@@ -0,0 +1,45 @@
1
+local M = {}
2
+
3
+-- define key table for input strings
4
+M.sym_map = {
5
+	s = "Find this symbol",
6
+	g = "Find this global definition",
7
+	c = "Find functions calling this function",
8
+	t = "Find this text string",
9
+	e = "Find this egrep pattern",
10
+	f = "Find this file",
11
+	i = "Find files #including this file",
12
+	d = "Find functions called by this function",
13
+	a = "Find places where this symbol is assigned a value",
14
+	b = "Build database",
15
+}
16
+
17
+M.default_keymaps = function(prefix)
18
+	local map = vim.keymap.set
19
+	local sym_map = M.sym_map
20
+	if MiniClue then
21
+		table.insert(MiniClue.config.clues, { mode = "n", keys = prefix, desc = "+cscope" })
22
+	else
23
+		local ok, wk = pcall(require, "which-key")
24
+		if ok then
25
+			if wk.add then
26
+				wk.add({ { prefix, group = "+cscope" } })
27
+			else
28
+				wk.register({ [prefix] = { name = "+cscope" } })
29
+			end
30
+		end
31
+	end
32
+	map({ "n", "v" }, prefix .. "s", "<cmd>CsPrompt s<cr>", { desc = sym_map.s })
33
+	map({ "n", "v" }, prefix .. "g", "<cmd>CsPrompt g<cr>", { desc = sym_map.g })
34
+	map({ "n", "v" }, prefix .. "c", "<cmd>CsPrompt c<cr>", { desc = sym_map.c })
35
+	map({ "n", "v" }, prefix .. "t", "<cmd>CsPrompt t<cr>", { desc = sym_map.t })
36
+	map({ "n", "v" }, prefix .. "e", "<cmd>CsPrompt e<cr>", { desc = sym_map.e })
37
+	map({ "n", "v" }, prefix .. "f", "<cmd>CsPrompt f<cr>", { desc = sym_map.f })
38
+	map({ "n", "v" }, prefix .. "i", "<cmd>CsPrompt i<cr>", { desc = sym_map.i })
39
+	map({ "n", "v" }, prefix .. "d", "<cmd>CsPrompt d<cr>", { desc = sym_map.d })
40
+	map({ "n", "v" }, prefix .. "a", "<cmd>CsPrompt a<cr>", { desc = sym_map.a })
41
+	map({ "n", "v" }, prefix .. "b", "<cmd>Cs db build<cr>", { desc = sym_map.b })
42
+	map({ "n", "v" }, "<C-]>", "<cmd>Cstag<cr>", { noremap = true, silent = true, desc = "ctag" })
43
+end
44
+
45
+return M
... ...
@@ -0,0 +1,125 @@
1
+local M = {}
2
+
3
+--- Check if given path is absolute path
4
+---@param path string
5
+---@return boolean
6
+M.is_path_abs = function(path)
7
+	return vim.startswith(path, "/")
8
+end
9
+
10
+--- Get relative path
11
+--- if "rel_to" or "path" are not absolute paths then return "path" as it is
12
+--- else return relative path of "path" wrt to "rel_to"
13
+---@param rel_to string
14
+---@param path string
15
+---@return string
16
+M.get_rel_path = function(rel_to, path)
17
+	if not M.is_path_abs(rel_to) or not M.is_path_abs(path) then
18
+		return path
19
+	end
20
+
21
+	local rel_path = ""
22
+	local sp_rel_to = vim.split(vim.fs.normalize(rel_to), "/")
23
+	local sp_path = vim.split(vim.fs.normalize(path), "/")
24
+	local len_rel_to = #sp_rel_to + 1
25
+	local len_path = #sp_path + 1
26
+	local i = 1
27
+
28
+	-- skip till parents are same
29
+	while i < len_rel_to and i < len_path do
30
+		if sp_rel_to[i] == sp_path[i] then
31
+			i = i + 1
32
+		else
33
+			break
34
+		end
35
+	end
36
+
37
+	-- append "../" for remaining parents
38
+	rel_path = rel_path .. string.rep("../", len_rel_to - i)
39
+
40
+	-- append remaining path
41
+	rel_path = rel_path .. table.concat(sp_path, "/", i)
42
+
43
+	return rel_path
44
+end
45
+
46
+--- Convert given path to absolute path
47
+---@param path string
48
+---@return string
49
+M.get_abs_path = function(path)
50
+	if M.is_path_abs(path) then
51
+		return path
52
+	end
53
+
54
+	local abs_path = vim.fs.joinpath(vim.fn.getcwd(), path)
55
+
56
+	return vim.fs.normalize(abs_path)
57
+end
58
+
59
+--- Get parent of given path
60
+---@param path string
61
+---@return string
62
+M.get_path_parent = function(path)
63
+	for parent in vim.fs.parents(path) do
64
+		return parent
65
+	end
66
+	return ""
67
+end
68
+
69
+---Get all dirs and files in given path
70
+---@param dir string
71
+---@return table
72
+M.get_files_in_dir = function(dir)
73
+	local fs_entries = vim.fn.readdir(dir)
74
+
75
+	-- add "/" suffix for dirs and return
76
+	return vim.tbl_map(function(x)
77
+		local entry = x
78
+		if dir ~= "." then
79
+			entry = vim.fs.joinpath(dir, x)
80
+		end
81
+		if vim.fn.isdirectory(x) == 1 then
82
+			entry = entry .. "/"
83
+		end
84
+		return entry
85
+	end, fs_entries)
86
+end
87
+
88
+---Get all dirs in given path
89
+---@param dir string
90
+---@return table
91
+M.get_dirs_in_dir = function(dir)
92
+	local fs_entries = vim.fn.readdir(dir)
93
+
94
+	-- add "/" suffix for dirs
95
+	fs_entries = vim.tbl_map(function(x)
96
+		local entry = x
97
+		if dir ~= "." then
98
+			entry = vim.fs.joinpath(dir, x)
99
+		end
100
+		return entry .. "/"
101
+	end, fs_entries)
102
+
103
+	-- return only dirs
104
+	return vim.tbl_filter(function(x)
105
+		return vim.fn.isdirectory(x) == 1
106
+	end, fs_entries)
107
+end
108
+
109
+M.is_path_same = function(path1, path2)
110
+	return path1 and path2 and M.get_abs_path(path1) == M.get_abs_path(path2)
111
+end
112
+
113
+---Opens file at given line number
114
+---@param fname string
115
+---@param lnum number
116
+M.open_file = function(fname, lnum)
117
+	if M.is_path_same(vim.api.nvim_buf_get_name(0), fname) then
118
+		-- change position when in same buffer
119
+		vim.api.nvim_win_set_cursor(0, { lnum, 0 })
120
+	else
121
+		vim.cmd(string.format("edit +%d %s", lnum, fname))
122
+	end
123
+end
124
+
125
+return M
... ...
@@ -0,0 +1,46 @@
1
+local M = {}
2
+
3
+M.lvl = vim.log.levels
4
+-- DEBUG
5
+-- ERROR
6
+-- INFO
7
+-- TRACE
8
+-- WARN
9
+-- OFF
10
+
11
+M.debug = function(msg, hide)
12
+	if hide then
13
+		return
14
+	end
15
+	vim.notify("cscope: " .. msg, M.lvl.DEBUG)
16
+end
17
+
18
+M.error = function(msg, hide)
19
+	if hide then
20
+		return
21
+	end
22
+	vim.notify("cscope: " .. msg, M.lvl.ERROR)
23
+end
24
+
25
+M.info = function(msg, hide)
26
+	if hide then
27
+		return
28
+	end
29
+	vim.notify("cscope: " .. msg, M.lvl.INFO)
30
+end
31
+
32
+M.trace = function(msg, hide)
33
+	if hide then
34
+		return
35
+	end
36
+	vim.notify("cscope: " .. msg, M.lvl.trace)
37
+end
38
+
39
+M.warn = function(msg, hide)
40
+	if hide then
41
+		return
42
+	end
43
+	vim.notify("cscope: " .. msg, M.lvl.WARN)
44
+end
45
+
46
+return M
... ...
@@ -0,0 +1,9 @@
1
+return {
2
+	SUCCESS = 0,
3
+	INVALID_OP = 1,
4
+	INVALID_EXEC = 2,
5
+	DB_NOT_FOUND = 3,
6
+	NO_RESULTS = 4,
7
+	INVALID_SYMBOL = 5,
8
+	NODE_NOT_FOUND = 6,
9
+}
0 10