Skip to content

fish-lsp: add fish-lsp support - #55

Open
bmalehorn wants to merge 4 commits into
mainfrom
brian/fish-lsp
Open

bmalehorn wants to merge 4 commits into
mainfrom
brian/fish-lsp

Conversation

@bmalehorn

@bmalehorn bmalehorn commented Oct 30, 2025

Copy link
Copy Markdown
Owner

I tried out fish-lsp and it mostly worked! Putting this PR out to show it off a bit & see if anyone has any ideas on how to fix the issues. They're probably solvable with a bit of time.

This is pretty awesome, thanks @ndonfris. Lmk if you know anything about these issues.

image

Fixes #36.

Things that work

  • definitions
  • hover
  • formatting
  • warnings

Issue: it's slow

Some actions take 5-10 seconds to run which is kind of slow. Since it indexes everything to begin with I don't really understand how looking up the definition of cd in the indexed location /opt/homebrew/Cellar/fish/4.1.2/share/fish/funtions/cd.fish could take 5-10 seconds, you'd think it'd just be some dictionary lookup.

If it hangs, that's the end of fish-lsp until you reload the window.

Something simple like adding set -l unused in config.fish takes 5-10 seconds to show "unused variable". Something fishy is going on. I am not sure what to run to diagnose why this is taking a while.

It may be indexing some big systems path location that might be too big. fish-lsp info --time-startup shows:

Server Start Time:                                                                    138.42 ms
Background Analysis Time:                                                             616.06 ms
Total Files Indexed:                                                                 1318 files
-----------------------------------------------------------------------------------------------
Indexed paths in `$fish_lsp_all_indexed_paths`:                                         2 paths
     [1]        | `/Users/brianmalehorn/.config/fish` |                                18 files
     [2]        | `/opt/homebrew/Cellar/fish/4.1.2/share/fish` |                     1300 files
-----------------------------------------------------------------------------------------------

I wonder if it's indexing all of these on every query.

Issue: slow formatter

This replaces the formatter, but the previous formatter ran almost instantly so it feels a lot slower. I might disable the formatting to use fish_indent again. This is mostly on me to figure out how to do that in vscode / disable it from fish-lsp.

Could have

In order of how much I want it:

  • verbose logging: it would be nice enable some kind of verbose mode on the server to see what it's doing & what messages its getting
  • socket / node-ipc: I also might want to run it in socket mode instead of stdio mode so it can handle concurrent requests, which may explain the freezing
  • tab completion: the LSP knows local variable names, so I'd figure it could tab complete them, but that doesn't happen today
  • semantic highlighting: a whole other topic, which I'll address later
  • fix "go to definition" on a bad function name giving an error: [Error - 9:10:55 PM] Request textDocument/definition failed. Message: Request textDocument/definition failed with message: Command failed: fish --command type -ap prompt_command Code: -32603 . When I run fish --command type -ap prompt_command I get the error fish: -ap: unknown option. I'm on the latest release 4.1.2. This is probably a bug in fish-lsp
  • fix "go to definition" on a bad function name giving an error: [Error - 9:47:25 PM] Request textDocument/hover failed. Message: Request textDocument/hover failed with message: ENOENT: no such file or directory, unlink '/var/folders/2k/ljy_ph75655_xk1lwbhlfrq80000gn/T/get-documentation.fish' Code: -32603. This is probably a bug in fish-lsp

Settings:
Many of these can be set with env variables like fish_lsp_foo=bar fish-lsp. I'll make a new setting to set env variables for fish-lsp.

  • document setting to disable all warnings: allow only showing definition lookup info without showing warnings
  • document setting to disable a list of warnings: a config setting to disable warning types X, Y, Z
  • document setting to add a list of paths to search: I imagine people will want to add their own directories. I'm not sure if it follows source commands by default.

Related

@bmalehorn bmalehorn mentioned this pull request Oct 30, 2025
@ndonfris

ndonfris commented Nov 5, 2025

Copy link
Copy Markdown

Some actions take 5-10 seconds to run which is kind of slow. Since it indexes everything to begin with I don't really understand how looking up the definition of cd in the indexed location /opt/homebrew/Cellar/fish/4.1.2/share/fish/funtions/cd.fish could take 5-10 seconds, you'd think it'd just be some dictionary lookup.

So there are two different cases here:

  • the dictionary for the current workspace + fish_lsp_all_indexed_paths (which is loaded into the dictionary depending on the workspace path)
  • outside of the startup dictionary/cache → lookup using shell

As you point out, I will look into how to add some form of a timeout utility


Something simple like adding set -l unused in config.fish takes 5-10 seconds to show "unused variable". Something fishy is going on. I am not sure what to run to diagnose why this is taking a while.

Does this happen when set -l unused is inside a function? Pretty sure currently, I just assume root level set variable definitions are expected to be exported, which shouldn't show unused diagnostic.

here's what happens in nvim

fish-lsp-vscode 1


tab completion: the LSP knows local variable names, so I'd figure it could tab complete them, but that doesn't happen today

Looks like this is a regression I'll look into. From my tests, local functions are still completed.


socket / node-ipc: I also might want to run it in socket mode instead of stdio mode so it can handle concurrent requests, which may explain the freezing

Take a look at the code for the current fish-lsp vscode extension. You should be able to achieve this by creating the corresponding LSP.connection seen in the file: src/server-module.ts

Also, you probably want to use server.initializationOptions like the current vscode extension for providing options.


A couple other questions I have:

  • Do the two error messages for hover/definition requests cause the server to crash?
  • What version of the server are you using?

@ndonfris

ndonfris commented Nov 5, 2025

Copy link
Copy Markdown

I wonder if it's indexing all of these on every query.

The server would only index all the files if the connection is needing to be started for a request, which might be happening in your case if the client is dropping the connection after a request completes.

Try making sure that the fish_lsp_log_file variable is set, and then you can see server logs with a command like:

tail -f (fish-lsp info --log-file)

@bmalehorn

bmalehorn commented Nov 16, 2025

Copy link
Copy Markdown
Owner Author

Something simple like adding set -l unused in config.fish takes 5-10 seconds to show "unused variable".

Does this happen when set -l unused is inside a function? Pretty sure currently, I just assume root level set variable definitions are expected to be exported, which shouldn't show unused diagnostic.

here's what happens in nvim

Details ![fish-lsp-vscode 1](https://private-user-images.githubusercontent.com/49458459/509884124-20695db7-4ec2-4413-bfea-61e1d2670ddf.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjMzMjgwNDksIm5iZiI6MTc2MzMyNzc0OSwicGF0aCI6Ii80OTQ1ODQ1OS81MDk4ODQxMjQtMjA2OTVkYjctNGVjMi00NDEzLWJmZWEtNjFlMWQyNjcwZGRmLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTExMTYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMTE2VDIxMTU0OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY0ZTZjODBiM2JlMWQzNzhhMzIwOTY2NzAyOGZhMjEwYTQ3MTllYTgyYTI2ZjdkNzlmNmE0MjY3YzhkODRjZjMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HBIkKfKe8yQ8dkR_eDAauCQitMam5K7222ooa3iORmE)

Hey @ndonfris , I did some more testing on this issue. It looks like fish-lsp slows down quite a bit on large files, and my config.fish is about 2000 lines long.

On a simple repro:

  1. make a fish file that's 2000 lines long
  2. edit the name of a variable
  3. try to go to definition on the variable

I see that it takes about 9 seconds to load the variable definition. Here's what that looks like:

Screen.Recording.2025-11-16.at.1.25.32.PM.mp4

I also tried it on a file that's 4000 lines long and it took 46 seconds. I think there's something O(n^2) going on with fish-lsp's parser, which is making it painfully slow on large files.

Do you have any idea what that is / would you mind profiling fish-lsp when it's loading a large file?

I can also file this as a formal ticket on https://github.com/ndonfris/fish-lsp.

big.fish
function big

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello

    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
    echo hello
end

set -l foo
echo $foo

@ndonfris

Copy link
Copy Markdown

After some quick testing locally, I have trouble reproducing such long input. What does your server $fish_lsp_log_file show when starting a new client connection that is taking super long?

tail -f (fish-lsp info --log-file) # will incrementally show logs 

Also,

fish-lsp info --time-startup

These should give clearer insight into what exactly is causing the server to take long.

Here's what I tested

demo

Also, here's how long startup takes on my config which has more than 35,000 total lines (although separated among multiple files)
Screenshot from 2025-11-17 23-51-43

Opening a ticket on https://github.com/ndonfris/fish-lsp is encouraged for visibility in case others are experiencing similar issues.

@ndonfris

Copy link
Copy Markdown

I did some more testing today, and it looks like the main bottleneck is in the diagnostic validation step. There are a couple locations that implement their own parser logic in the server, so this was not clear to me initially. In particular, diagnostic checks like unreachable block validation (error code 5555) seem to be a large part of the cost. I’ll dig into this further, but there may be some room for optimization there.

For very large files (4,000+ lines), there’s also a practical limit to how fast diagnostics can reasonably be computed. On my machine, a single pass over a ~4k-line document using tree-sitter, with generators and every applicable optimization I can think of (and without doing any higher-level analysis like building FishSymbol, SemanticToken, Diagnostic, etc.), still comes out to around ~500ms.

Here is what I used to test the server:

big.fish
# here's how I built big.fish
echo 'function foo' > /tmp/big.fish 
for i in (seq 1 1000)
    echo "echo $i" >> /tmp/big.fish 
end
echo "end" >>/tmp/big.fish

echo 'function bar' >>/tmp/big.fish 
for i in (seq 1 1000)
    echo "function __bar_$i -d 'inner bar $i'; echo $i; end;" >> /tmp/big.fish
end
echo 'end' >> /tmp/big.fish

for v in (seq 1 3000)
    set flags (random choice '-l' '-g' '-xg' '-U' '-lx' '-lq' '-gq' '--global' '--export')
    set char (random choice a b c d e f g h i j k l m n o p q r s t u v w x y z)
    set repeat (random 5 10)
    set value (string repeat -n $repeat -- $char)
    echo "set $flags var_$v $value" >> /tmp/big.fish 
end

for i in (seq 1 2000)
    set char (random choice a b c d e f g h i j k l m n o p q r s t u v w x y z)
    echo "for $char in (seq 1 $i); echo \$$char; end" >> /tmp/big.fish 
end 
fish_indent --only-indent /tmp/big.fish
# cp /tmp/big.fish (fish-lsp info --path)/tests/workspace/profiling/big1.fish
tests/big-file.test.ts
// tests/big-file.test.ts

import { analyzer, Analyzer } from '../src/analyze';
import TestWorkspace from './test-workspace-utils';
import { LspDocument } from '../src/document';
import path from 'path';
import { nodesGen } from '../src/utils/tree-sitter';
describe('Large Workspace Analysis', () => {
  let workspace = TestWorkspace.create({ name: 'large-workspace' }).addDocuments(
    LspDocument.createFromPath(path.join(__dirname, 'workspaces/profiling/big1.fish')),
  ).initialize();
  let docBig1: LspDocument;

  beforeAll(async () => {
    await Analyzer.initialize();
    docBig1 = workspace.getDocument('big1.fish')!;
  });

  describe('node analysis', () => {
    it('retrieve many variable nodes from big1.fish', () => {

      const startTime = performance.now();
      const cached = analyzer.analyze(docBig1);
      let i = 0;
      for (const node of nodesGen(cached.root!)) {
        i++;
      }
      const endTime = performance.now();
      console.log(`Traversed nodes in ${(endTime - startTime).toFixed(2).toString()} ms`);
      console.log(`Total nodes: ${i}`);
      console.log(`Total lines: ${docBig1.lineCount}`);
    });
  });
});
test output Screenshot from 2025-11-18 19-08-39

A more structural fix would be to move all analysis/diagnostic work into a background job running on a separate thread so the main server thread stays responsive. That’s a fairly large refactor, though. If you’re interested in exploring that direction, I’d definitely be open to a fork + PR.

As a workaround, completely disabling diagnostics for very large files could help, but that would need a PR too, since diagnostics are currently always computed on document change.


Are you opposed to grouping related sections of your config.fish into separate configuration snippet files in $__fish_user_dir/conf.d/*.fish?

My screenshot showing the server analyzing my $__fish_user_dir with ~35000 total lines in the previous response is using this approach and I rarely notice any performance hiccups. The behavior is effectively the same as putting everything in config.fish, but it makes the workspace much easier for the server to index, and a lot of features (like workspace symbols) are designed with this layout in mind.

@EmilyGraceSeville7cf

Copy link
Copy Markdown
Contributor

This is amazing to see at least some of LSP stuff working right in VS Code. 😄 I expected it to happen for a long time and working LSP for me is a great argument to start using Fish again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create LSP for Fish

3 participants