No description
Find a file
2025-08-12 17:34:14 +02:00
infra add example 2025-08-12 17:14:02 +02:00
sharedproducts/servicebus add example 2025-08-12 17:14:02 +02:00
src add example 2025-08-12 17:14:02 +02:00
tests add example 2025-08-12 17:14:02 +02:00
azure.yaml add example 2025-08-12 17:14:02 +02:00
FzFLuaDemo.sln add example 2025-08-12 17:14:02 +02:00
nuget.config add example 2025-08-12 17:14:02 +02:00
README.md update readme 2025-08-12 17:34:14 +02:00

Ciao Telescope! I don't need you anymore!

Disclaimer: being a software engineer / consultant in the enterprise myself, I am perfectly aware of how expensive software development can get and the value of open source contributions. Telescope is a wonderful piece of software and that we don't have to pay for it is a miracle. The following tutorial therefore doesn't aim to bash telescope but to show alternatives that might fit your needs even better.

Use Case

Trying to find the projects in my solution that use a certain nuget package, that goes by the name ramboe.Utils.Testing

The Problem

telescope yields way too many results due to fuzzy finding

What I actuall want and what we will achieve fairly easily with fzf-lua is an exact search string matching:

How I'd do it with telescope

  1. live grep: ramboe.Utils.Testing

  2. send all to quickfix

  3. :Telescope quickfix

  4. filter through .csproj

  5. send to quickfix again

Telescope and the thing with the exact string matching

Apparently it is not possible to have a strict search in Telescope.

chatgpt

First gives me additional_args that don't do anything

-- init.lua

require('telescope').setup {
  defaults = {
    -- sorter = ...
  },
  pickers = {
    -- ...
    find_files = {
      -- doesn't work
      additional_args = function()
        return { "--fixed-strings" } -- disables regex & fuzzy, matches literally
      end,
      theme = "dropdown",
    }
  },
  extensions = {
    -- ...
  }
}

and then tells me to hardcode file extensions:

reddit

doesn't look much better either.

fzf-lua: enabling exact string matching is easy

If we want to use exact string matching, but only for the files picker, we would set it up like this

-- init.lua

require("fzf-lua").setup({
  -- ...
  -- use exact string matching, but only for the files picker
  files = {
    fzf_opts = {
      ['--exact'] = '',
      ['--no-sort'] = '',
    }
  },
  -- ...
})

Now I really only see the files I want to see

I can now send them to the quickfix window with ctrl+q, and then live_grep over those files with lgrep_quickfix

Install and set up fzf-lua

Install

fzf-lua is a wrapper around fzf. Make sure fzf is installed in your system

As the README tells us: Inside your lazy.nvim configuration add these lines

-- lua/plugins/init.lua
-- ...

  {
    "ibhagwan/fzf-lua",
    -- optional for icon support
    dependencies = { "nvim-tree/nvim-web-devicons" },
    -- or if using mini.icons/mini.nvim
    -- dependencies = { "echasnovski/mini.icons" },
    opts = {}
  },

-- ...

Now, in order to use fzf-lua after the installation, try a keymap to the files picker like so:

vim.keymap.set("n", "<leader>ff", require("fzf-lua").files, { noremap = true, silent = true })

Check out all the pickers with :lua require("fzf-lua").builtin()

Fixing csharp_ls

The Problem

After Install and set up fzf-lua we notice that everything works flawless with lua-language-server and omnisharp but not with csharp_ls:

csharp_ls: -32602 System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'lineNumber')

stacktrace...

csharp_ls: -32602: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (
Parameter 'lineNumber')
   at Microsoft.CodeAnalysis.Text.CompositeText.CompositeTextLineInfo.get_Item(Int32 lineNumber)
   at Microsoft.CodeAnalysis.Text.TextLineCollection.GetLineFromPosition(Int32 position)
   at Microsoft.CodeAnalysis.Text.TextLineCollection.GetLinePosition(Int32 position)
   at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.GetLinePosition(Int32 position, CancellationToken cancellati
onToken)
   at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.GetLineSpan(TextSpan span, CancellationToken cancellationTok
en)
   at Microsoft.CodeAnalysis.SourceLocation.GetLineSpan()
   at CSharpLanguageServer.Conversions.Diagnostic.fromRoslynDiagnostic(Diagnostic diagnostic) in /_//src/CSharpLa
nguageServer/Conversions.fs:line 237
   at CSharpLanguageServer.Handlers.Diagnostic.diagnostics@71-1.Invoke(Diagnostic diagnostic)
   at Microsoft.FSharp.Collections.Internal.IEnumerator.map@75.DoMoveNext(b& curr) in D:\workspace\_work\1\s\src\
fsharp\FSharp.Core\seq.fs:line 77
   at Microsoft.FSharp.Collections.Internal.IEnumerator.MapEnumerator1.System.Collections.IEnumerator.MoveNext()
 in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\seq.fs:line 64
   at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
   at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable1 source) in D:\workspace\_work\1\s\src\fshar
p\FSharp.Core\seq.fs:line 825
   at CSharpLanguageServer.Handlers.Diagnostic.handle@67-66.Invoke(SemanticModel semanticModelMaybe) in /_//src/C
SharpLanguageServer/Handlers/Diagnostic.fs:line 70
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation1 ctxt, b result1
, FSharpFunc2 userCode) in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 464
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc2 firstAction) in D:\workspace\_work\1\s\src\fsharp\
FSharp.Core\async.fs:line 104

Explanation

First we need to have a quick look under the hood:

:help 'buftype'

https://neovim.io/doc/user/options.html

fzf-lua works with preview buffers, e.g. to display search results, those are then also sent to the LSP.

csharp_ls is receiving a position (line number) from Neovim that doesn't exist in the underlying document. It's trying to map a buffer position to a line number, but failing — often caused by an invalid buffer state.

csharp_ls is trying to return or parse diagnostics for a file that it thinks has N lines, but the line number sent from Neovim is beyond that.

fzf-lua can cause this in the following ways:

  • It opens preview buffers or temporary buffers in the background.

  • It may focus or jump to locations in files temporarily during fuzzy find.

These actions can trigger LSP features like diagnostics, document highlights, etc. even for unsaved or virtual buffers.

Fix

In essence we need to tell neovim to only load csharp_ls on actual .cs files.

-- lua/configs/lspconfig.lua
-- ..

-- Create a wrapper on_attach for csharp_ls
local function csharp_on_attach(client, bufnr)
  -- disable diagnostics for non .cs files
  if client.name == "csharp_ls" then
    
    local ft = vim.bo[bufnr].filetype
    local bt = vim.bo[bufnr].buftype
    
    if ft ~= "cs" or bt ~= "" then
      vim.diagnostic.disable(bufnr)
    end
  end

  -- still call the default on_attach
  on_attach(client, bufnr)
end

lspconfig.csharp_ls.setup({
  on_attach = csharp_on_attach,
  --...
})

You can find the changes I made to my nvim config here.

Fixing omnisharp

https://github.com/OmniSharp/omnisharp-roslyn/releases/tag/v1.39.14

Intro

I use nvchad and installed the omnisharp language server with Mason. When I then try to open a .cs file it says

/usr/share/nvim/runtime/lua/vim/lsp/rpc.lua:800: Spawning language server with cmd: `{ "omnisharp", "--languagese
rver", "--hostPID", "3425232", "-z", "--hostPID", "3425232", "DotNet:enablePackageRestore=false", "--encoding", "
utf-8", "--languageserver", "Sdk:IncludePrereleases=true", "FormattingOptions:EnableEditorConfigSupport=true" }`
failed. The language server is either not installed, missing from PATH, or not executable

Fix

~/.local/share/nvim/mason/packages/

OmniSharp vs omnisharp

lspconfig.omnisharp.setup({
  cmd = { "OmniSharp", "--languageserver", "--hostPID", tostring(pid) },
  on_attach = on_attach,
  on_init = on_init,
  capabilities = capabilities,
})

fzf-lua Customization

With :FzfLua profiles you can choose between different profiles. Choosing a profile affects UI but also key bindings and basically everything you can configure in fzf-lua.

If you wanted to have a UI that looks like telescope and wanted the typical telescope bindings (like ctrl+q), you would choose the telescope profile.

I personally like to stick to the default profile and configure just what I need. This is done by calling the require("fzf-lua").setup() function:

-- init.lua

-- you only need this area if you want to configure fzf-lua
require("fzf-lua").setup({
  -- preview window is in fullscreen and takes 70% of the space and is above the search results
  winopts = {
    fullscreen = true,
    preview = {
      layout = "vertical",
      vertical = "up:70%",
    },
  },

  -- use exact string matching, but only for the files picker
  files = {
    fzf_opts = {
      ['--exact'] = '',
      ['--no-sort'] = '',
    }
  },

  -- use cltr-q to select all items and convert to quickfix list (as in telescope)
  keymap = {
    fzf = {
      ["ctrl-q"] = "select-all+accept",
    },
  },
})

The ways in which fzf-lua can be configured can be found here. If you don't care much about the specifics you might want to try some of the profiles that come with fzf-lua.

fzf-lua: there's more!

In fzf-lua, after live grepping through your files / the quickfix window, you can then press ctrl+g to fuzzy search through the results

to eventually only look into the files you actually want to look into

final workflow

  1. live grep: ramboe.Utils.Testing"

  2. ctrl+g

  3. grep: .csproj

you can find my whole nvim config here