Skip to content
102 changes: 48 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ Cached treesitter navigation on a big projects, an attempt to make navigation in
## Description

_hopcsharp_ is a lightweight code navigation tool inspired by [ctags](https://github.com/universal-ctags/ctags), built
for large C# projects. It uses [tree-sitter](https://tree-sitter.github.io/tree-sitter/) to quickly (not blazing fast
but still good) parse code and store marks in a SQLite database for fast access, after that you can navigate freely in
code base using built in methods or writing queries on your own against sqlite database.
for large C# projects. It uses [tree-sitter](https://tree-sitter.github.io/tree-sitter/) to quickly parse code and
store marks in a SQLite database for fast access, after that you can navigate freely in
code base using built in methods or writing queries on your own against sqlite database. Important to understand that this is
not an _LSP server_ this tool is intended for code navigation and read. It won't be as precise as full blown _LSP server_ but
it won't require you to compile code as well :) so basically any c# codebase can be opened and parsed with this plugin.

__This plugin is in its early stages__ expect lots of bugs :D, I hope that there will be people's interest and
I hope that there will be people's interest and
contributions as well. I'll try to improve it little by little.

<p align="center">
<img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExN3Q0YTdkNWkxb2Z0d216eW5rcHB0N2dxd2htYXZiZGphbTZkNGRxdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HBGku9Wu9y0CtgXuCF/giphy.gif" />
</p>
[![bvT1Q.gif](https://s12.gifyu.com/images/bvT1Q.gif)](https://gifyu.com/image/bvT1Q)

## How does it work?

Expand All @@ -39,6 +39,31 @@ Using [packer.nvim](https://github.com/wbthomason/packer.nvim):
use({ 'leblocks/hopcsharp.nvim', requires = { { 'kkharji/sqlite.lua' } } })
```

## Quick Start

Example keybinding configuration:

```lua
local hopcsharp = require('hopcsharp')

-- database
vim.keymap.set('n', '<leader>hD', hopcsharp.init_database, { desc = 'hopcsharp: init database' })

-- navigation
vim.keymap.set('n', '<leader>hd', hopcsharp.hop_to_definition, { desc = 'hopcsharp: go to definition' })
vim.keymap.set('n', '<leader>hi', hopcsharp.hop_to_implementation, { desc = 'hopcsharp: go to implementation' })
vim.keymap.set('n', '<leader>hr', hopcsharp.hop_to_reference, { desc = 'hopcsharp: go to reference' })
vim.keymap.set('n', '<leader>ht', hopcsharp.get_type_hierarchy, { desc = 'hopcsharp: type hierarchy' })

-- fzf pickers (requires fzf-lua)
local pickers = require('hopcsharp.pickers.fzf')
vim.keymap.set('n', '<leader>hf', pickers.source_files, { desc = 'hopcsharp: find source files' })
vim.keymap.set('n', '<leader>ha', pickers.all_definitions, { desc = 'hopcsharp: all definitions' })
vim.keymap.set('n', '<leader>hc', pickers.class_definitions, { desc = 'hopcsharp: class definitions' })
vim.keymap.set('n', '<leader>hn', pickers.interface_definitions, { desc = 'hopcsharp: interface definitions' })
vim.keymap.set('n', '<leader>he', pickers.enum_definitions, { desc = 'hopcsharp: enum definitions' })
```

## API
This plugin exposes only a small set of functions, allowing you to build various interfaces and workflows on top of them.

Expand Down Expand Up @@ -91,56 +116,25 @@ Opens read-only buffer with type hierarchy.
require('hopcsharp').get_db()
```

Returns opened _[sqlite_db](https://github.com/kkharji/sqlite.lua/blob/50092d60feb242602d7578398c6eb53b4a8ffe7b/doc/sqlite.txt#L76)_ object, you can create custom flows querying it with SQL queries from lua. See customization

## Example customizations
Returns opened _[sqlite_db](https://github.com/kkharji/sqlite.lua/blob/50092d60feb242602d7578398c6eb53b4a8ffe7b/doc/sqlite.txt#L76)_ object, you can create custom flows querying it with SQL queries from lua. See `:h hopcsharp.get_db` for more details.

[Here](https://github.com/leblocks/dotfiles/blob/master/packages/neovim/config/lua/plugins/hopcsharp.lua) (this is my
configuration that I use day to day) you can take a look at example configuration based on _get_db()_ method and _[fzf-lua](https://github.com/ibhagwan/fzf-lua)_,
here is demo usage of it on a [net framework reference source](https://github.com/microsoft/referencesource) repository
## FZF Pickers

<p align="center">
<img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExODhzM2JnMTc1eGZ0ajB5cjFvNXF5ZDV1aDFkbG5saWhwcGo4a3o2ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HXFp3DblkKtrcOBn8J/giphy.gif" />
</p>
hopcsharp ships with built-in [fzf-lua](https://github.com/ibhagwan/fzf-lua) pickers for browsing definitions and source files. Requires the `fzf-lua` plugin to be installed.

Create repository _.cs_ files fzf-lua picker that were previously stored in a db:
All pickers are available via `require('hopcsharp.pickers.fzf')`:

<p align="center">
<img src="https://media.giphy.com/media/jvaFNuMIKjvHsACiBM/giphy.gif" />
</p>
| Picker | Description |
|---|---|
| `source_files` | Browse all `.cs` source files in the database |
| `all_definitions` | Browse all definitions |
| `class_definitions` | Browse class definitions |
| `interface_definitions` | Browse interface definitions |
| `method_definitions` | Browse method definitions |
| `struct_definitions` | Browse struct definitions |
| `enum_definitions` | Browse enum definitions |
| `record_definitions` | Browse record definitions |
| `attribute_definitions` | Browse attribute definitions |

```lua
local list_files = function()
-- get database (connection is always opened)
fzf_lua.fzf_exec(function(fzf_cb)
coroutine.wrap(function()
local db = hopcsharp.get_db()
local co = coroutine.running()
local items = db:eval([[ SELECT path FROM files ]])

if type(items) ~= 'table' then
items = {}
end

for _, entry in pairs(items) do
fzf_cb(entry.path, function() coroutine.resume(co) end)
coroutine.yield()
end
fzf_cb()
end)()
end, {
actions = {
["enter"] = actions.file_edit_or_qf,
["ctrl-s"] = actions.file_split,
["ctrl-v"] = actions.file_vsplit,
["ctrl-t"] = actions.file_tabedit,
["alt-q"] = actions.file_sel_to_qf,
["alt-Q"] = actions.file_sel_to_ll,
["alt-i"] = actions.toggle_ignore,
["alt-h"] = actions.toggle_hidden,
["alt-f"] = actions.toggle_follow,
}
})
end
```
See `:h hopcsharp-fzf-pickers` for more details.

115 changes: 111 additions & 4 deletions doc/hopcsharp.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
==============================================================================
INTRODUCTION *hopcsharp.txt
INTRODUCTION *hopcsharp.txt*

Author: a.f.gurevich@gmail.com

Expand All @@ -14,9 +14,10 @@ CONTENTS *hopcsharp-contents*
3. Installation .............. |hopcsharp-installation|
4. Configuration ............. |hopcsharp-configuration|
5. API ....................... |hopcsharp-api|
6. Commands .................. |hopcsharp-commands|
7 Schema .................... |hopcsharp-database|
8. Troubleshooting ........... |hopcsharp-troubleshooting|
6. FZF Pickers ............... |hopcsharp-fzf-pickers|
7. Commands .................. |hopcsharp-commands|
8. Schema .................... |hopcsharp-database|
9. Troubleshooting ........... |hopcsharp-troubleshooting|


==============================================================================
Expand Down Expand Up @@ -232,6 +233,112 @@ Example: >
]], { name = 'MyClassName' })
<

==============================================================================
FZF PICKERS *hopcsharp-fzf-pickers*

hopcsharp.nvim provides built-in fzf-lua pickers for navigating definitions
and source files. These require the |fzf-lua| plugin to be installed.
https://github.com/ibhagwan/fzf-lua

All pickers support the following keybindings:
- `enter` - open file
- `ctrl-s` - open in horizontal split
- `ctrl-v` - open in vertical split
- `ctrl-t` - open in new tab
- `alt-q` - send selection to quickfix list


------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.source_files*
hopcsharp.pickers.fzf.source_files()

Opens fzf-lua picker listing all `.cs` source files stored in the database.

Example: >
require('hopcsharp.pickers.fzf').source_files()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.all_definitions*
hopcsharp.pickers.fzf.all_definitions()

Opens fzf-lua picker listing all definitions (classes, interfaces, methods,
structs, enums, records, and attributes).

Example: >
require('hopcsharp.pickers.fzf').all_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.class_definitions*
hopcsharp.pickers.fzf.class_definitions()

Opens fzf-lua picker listing class definitions.

Example: >
require('hopcsharp.pickers.fzf').class_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.interface_definitions*
hopcsharp.pickers.fzf.interface_definitions()

Opens fzf-lua picker listing interface definitions.

Example: >
require('hopcsharp.pickers.fzf').interface_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.method_definitions*
hopcsharp.pickers.fzf.method_definitions()

Opens fzf-lua picker listing method definitions.

Example: >
require('hopcsharp.pickers.fzf').method_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.struct_definitions*
hopcsharp.pickers.fzf.struct_definitions()

Opens fzf-lua picker listing struct definitions.

Example: >
require('hopcsharp.pickers.fzf').struct_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.enum_definitions*
hopcsharp.pickers.fzf.enum_definitions()

Opens fzf-lua picker listing enum definitions.

Example: >
require('hopcsharp.pickers.fzf').enum_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.record_definitions*
hopcsharp.pickers.fzf.record_definitions()

Opens fzf-lua picker listing record definitions.

Example: >
require('hopcsharp.pickers.fzf').record_definitions()
<

------------------------------------------------------------------------------
*hopcsharp.pickers.fzf.attribute_definitions*
hopcsharp.pickers.fzf.attribute_definitions()

Opens fzf-lua picker listing attribute definitions.

Example: >
require('hopcsharp.pickers.fzf').attribute_definitions()
<

==============================================================================
DATABASE SCHEMA *hopcsharp-database*

Expand Down
11 changes: 11 additions & 0 deletions lua/hopcsharp/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,21 @@ local function check_treesitter_c_sharp_grammar_installation()
end
end

local function check_fzf_lua_optional()
vim.health.start('fzf-lua [optional]')
local ok, _ = pcall(require, 'fzf-lua')
if ok then
vim.health.ok('fzf-lua is installed')
else
vim.health.error('fzf-lua is not installed')
end
end

M.check = function()
check_fd()
check_sqlite_installation()
check_treesitter_c_sharp_grammar_installation()
check_fzf_lua_optional()
end

return M
40 changes: 1 addition & 39 deletions lua/hopcsharp/hop/init.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local utils = require('hopcsharp.utils')
local hop_utils = require('hopcsharp.hop.utils')
local dbutils = require('hopcsharp.database.utils')

Expand All @@ -8,42 +7,6 @@ local implementation_providers = require('hopcsharp.hop.providers.implementation

local M = {}

local stop_callback = nil

local function populate_quickfix(entries, jump_on_quickfix, type_converter)
-- stop previous quickfix population
-- won't work 100% but it much better
-- rathen that nothing
if stop_callback then
stop_callback()
stop_callback = nil
end

-- remove previous quickfix entries
vim.fn.setqflist({}, 'r')

utils.__scheduled_iteration(entries, function(i, item, _, stop)
if i == 1 then
stop_callback = stop
end

vim.fn.setqflist({
{
filename = item.path,
lnum = item.row + 1,
col = item.col,
text = string.format('%-15s | %s', type_converter(item.type), item.namespace or ''),
},
}, 'a')
end)

vim.cmd([[ :copen ]])

if jump_on_quickfix then
vim.cmd([[ :cc! ]])
end
end

local function filter_entry_under_cursor(entries)
local filtered_entries = {}
local current_line = vim.fn.getcurpos()[2] -- 2 for line number
Expand Down Expand Up @@ -98,9 +61,8 @@ M.__hop_to = function(hop_providers, config)

-- sent to quickfix if there is too much
if #filtered_items > 1 then
-- TODO cover this in test
local converter = type_converter or dbutils.get_type_name
populate_quickfix(filtered_items, jump_on_quickfix, converter)
hop_utils.__populate_quickfix(filtered_items, jump_on_quickfix, converter)
end

return
Expand Down
38 changes: 38 additions & 0 deletions lua/hopcsharp/hop/utils.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
local utils = require('hopcsharp.utils')

local M = {}

local stop_callback = nil

M.__open_buffer = function(path, exists_callback, not_exists_callback)
local buffers = vim.api.nvim_list_bufs()
-- check if the file is already open in any buffer
Expand Down Expand Up @@ -54,4 +58,38 @@ M.__thop = function(path, row, column)
vim.fn.setcursorcharpos(row, column + 1)
end

M.__populate_quickfix = function(entries, jump_on_quickfix, type_converter)
-- stop previous quickfix population
-- won't work 100% but it much better
-- rathen that nothing
if stop_callback then
stop_callback()
stop_callback = nil
end

-- remove previous quickfix entries
vim.fn.setqflist({}, 'r')

utils.__scheduled_iteration(entries, function(i, item, _, stop)
if i == 1 then
stop_callback = stop
end

vim.fn.setqflist({
{
filename = item.path,
lnum = item.row + 1,
col = item.col,
text = string.format('%-15s | %s', type_converter(item.type), item.namespace or ''),
},
}, 'a')
end)

vim.cmd([[ :copen ]])

if jump_on_quickfix then
vim.cmd([[ :cc! ]])
end
end

return M
Loading