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 ` - `:Cstag ` does `tags` search if no results are found in `cscope`. - Empty `` can be used in `:Cs` and `:Cstag` to pick `` as sym. - Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable. - Keymaps can be disabled using `disable_maps` option. - `:CsPrompt ` 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 ` add db file(s) to cscope search. - `:Cs db rm ` 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 :: -d :: ...` - `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 ` Opens "downward" stack showing all the functions who call the ``. - `:CsStackView open up ` Opens "upward" stack showing all the functions called by the ``. - In `CsStackView` window, use following keymaps - `` toggle child under cursor - `` open location of symbol under cursor - `` open location of symbol under cursor in vertical split - `` open location of symbol under cursor in horizontal split - `q` or `` close window - `` or `` scroll up preview - `` or `` 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 = "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 "" 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 `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 `` can be configured using `prefix` option. Default value for prefix is `c`. (Try setting it to `C-c` 😉) | Keymaps | Description | | ----------------- | --------------------------------------------------- | | `s` | find all references to the token under cursor | | `g` | find global definition(s) of the token under cursor | | `c` | find all calls to the function name under cursor | | `t` | find all instances of the text under cursor | | `e` | egrep search for the word under cursor | | `f` | open the filename under cursor | | `i` | find files that include the filename under cursor | | `d` | find functions that function under cursor calls | | `a` | find places where this symbol is assigned a value | | `b` | build cscope database | | Ctrl-] | do `:Cstag ` | ### Custom Keymaps Disable default keymaps by setting `disable_maps = true`. There are 2 ways to add keymaps for `Cscope`. #### Using `:CsPrompt` command `:CsPrompt ` 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" }, "", "CsPrompt g") ``` #### 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" }, "", "Cs f g") ``` ## 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 . **Adds cscope support for Neovim** cscope_maps.nvim.v2.webm REQUIREMENTS *cscope_maps-cscope_maps.nvim-requirements* - Neovim >= 0.10 - cscope 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 ` - `:Cstag ` does `tags` search if no results are found in `cscope`. - Empty `` can be used in `:Cs` and `:Cstag` to pick `` as sym. - Supports `cscope` and `gtags-cscope`. Use `cscope.exec` option to specify executable. - Keymaps can be disabled using `disable_maps` option. - `:CsPrompt ` 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 and 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 ` add db file(s) to cscope search. - `:Cs db rm ` 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 - e.g. user script will be called as following - - `${db_build_cmd.script} ${db_build_cmd.args} -d :: -d :: ...` - `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 ` Opens "downward" stack showing all the functions who call the ``. - `:CsStackView open up ` Opens "upward" stack showing all the functions called by the ``. - In `CsStackView` window, use following keymaps - `` toggle child under cursor - `` open location of symbol under cursor - `` open location of symbol under cursor in vertical split - `` open location of symbol under cursor in horizontal split - `q` or `` close window - `` or `` scroll up preview - `` or `` 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 >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 = "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 "" 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 `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 ~ `` can be configured using `prefix` option. Default value for prefix is `c`. (Try setting it to `C-c` ) ----------------------------------------------------------------------- Keymaps Description ----------------- ----------------------------------------------------- s find all references to the token under cursor g find global definition(s) of the token under cursor c find all calls to the function name under cursor t find all instances of the text under cursor e egrep search for the word under cursor f open the filename under cursor i find files that include the filename under cursor d find functions that function under cursor calls a find places where this symbol is assigned a value b build cscope database Ctrl-] do :Cstag ----------------------------------------------------------------------- CUSTOM KEYMAPS ~ Disable default keymaps by setting `disable_maps = true`. There are 2 ways to add keymaps for `Cscope`. USING :CSPROMPT COMMAND `:CsPrompt ` 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" }, "", "CsPrompt g") < 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" }, "", "Cs f g") < 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 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 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 ::" 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 |rm |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(""), 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 = "" if vim.tbl_contains({ "f", "i", "7", "8" }, op) then arg = "" 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 "" if M.opts.tag.keymap == true then vim.keymap.set({ "n", "v" }, "", "Cstag", { 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", "", M.toggle_win, opts) -- toggle children vim.keymap.set("n", "", M.toggle_children, opts) -- open location under cursor vim.keymap.set("n", "", function() M.open_action("none") end, opts) vim.keymap.set("n", "", function() M.open_action("vert") end, opts) vim.keymap.set("n", "", function() M.open_action("horiz") end, opts) -- scroll up vim.keymap.set("n", "", M.pv_scroll(-1), opts) vim.keymap.set("n", "", M.pv_scroll(-1), opts) -- scroll down vim.keymap.set("n", "", M.pv_scroll(1), opts) vim.keymap.set("n", "", 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 = "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", "CsPrompt s", { desc = sym_map.s }) map({ "n", "v" }, prefix .. "g", "CsPrompt g", { desc = sym_map.g }) map({ "n", "v" }, prefix .. "c", "CsPrompt c", { desc = sym_map.c }) map({ "n", "v" }, prefix .. "t", "CsPrompt t", { desc = sym_map.t }) map({ "n", "v" }, prefix .. "e", "CsPrompt e", { desc = sym_map.e }) map({ "n", "v" }, prefix .. "f", "CsPrompt f", { desc = sym_map.f }) map({ "n", "v" }, prefix .. "i", "CsPrompt i", { desc = sym_map.i }) map({ "n", "v" }, prefix .. "d", "CsPrompt d", { desc = sym_map.d }) map({ "n", "v" }, prefix .. "a", "CsPrompt a", { desc = sym_map.a }) map({ "n", "v" }, prefix .. "b", "Cs db build", { 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, }