How to optimize code-completion of vshaxe?

I’m developing a large haxe project using vscode/vshaxe. I’m troubled that code-completion is very slow in most cases.

I tried this settings:

{
    "editor.quickSuggestions": {
        "other": false,
        "comments": false,
        "strings": false
    },
    "editor.parameterHints.enabled": false,
    "editor.hover.enabled": false,
    "editor.suggestOnTriggerCharacters": false,

    "haxe.buildCompletionCache": true,
    "haxe.enableDiagnostics": false,
    "haxe.enableSignatureHelpDocumentation": false,
    "haxe.diagnosticsPathFilter": "${workspaceRoot}/src|${haxelibPath}"
}

Is there any way to make “typing” faster?

Hm, that’s indeed quite bad… How big is that project?

vshaxe relies on the completion cache to skip as much typing / macros / etc as possible during Haxe completion requests. Clearly this isn’t working out in your case though.

I’m surprised the “typing” node isn’t expandable with how much time it takes. What about “macro” though? Perhaps one of the macros that runs invalidates a lot of types (and the results of the macro aren’t cached either, so it’s re-run each time)?

To debug invalidation, you can add this to your settings:

"haxe.displayServer": {
	"arguments": ["-v"]
}

And then observe the reusing / skipping messages in the output channel.

Note that completion could still be slow if there’s only “reusing” messages (in case some modules aren’t cached to begin with, there won’t be a “skipping”).

With latest Haxe, you can also use the “Cache” view in the “Haxe Server” activity bar menu to observe what exactly has been cached, what dependencies the types have etc.

In my experience, one possible cause for the “typing” slowdown when using macros is redundant typing caused by “unfenced” imports and type references. If you’re not careful, you can easily get all your code and libs processed twice: once in normal compilation context and once in the macro context. In general all the macro code must reside in a separate module that only imports what’s needed for macros (usually just the haxe.macro. stuff), also don’t forget import.hx - you might need to add some #if !macro there.

That said, “completion” is supposed to be very lazy though, so I’m wondering how can it take 6 seconds… O_o And even if my double-typing guess is correct, 3 seconds is still too much, so this needs more investigation.

Thanks your reply!
Our project is using Haxe4.0.0-rc.2, OpenFL 8.9.0, Lime 7.3.0, Starling 2.5.1 and some tiny libs.

In general all the macro code must reside in a separate module that only imports what’s needed for macros (usually just the haxe.macro. stuff), also don’t forget import.hx - you might need to add some #if !macro there.

Hm…, maybe this should have been done, but I’ll check again.

I got a log from starting the language server to running the first input completion.
Please see it.

Haxe language server started
Listening on port 6000
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--display,{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"supportsResolve":true}}]
Completion Response =
{"jsonrpc":"2.0","id":0,"result":{"result":{"methods":["display/definition","server/contexts","server/invalidate","server/module","server/files","initialize","display/completion","typer/compiledTypes","display/references","server/modules","display/completionItem/resolve","display/typeDefinition","server/memory","display/hover","display/package","display/signatureHelp","server/readClassPaths","server/configure"],"haxeVersion":{"major":4,"minor":0,"patch":0,"pre":"rc.2","build":"77068e10c"},"protocolVersion":{"major":0,"minor":2,"patch":0}},"timestamp":1554687490.9749486}}
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Building Cache...
Compacted memory 0.002s 0.3MB
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":1,"method":"server/configure","params":{"noModuleChecks":true,"print":{"completion":false,"reusing":false}}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [complete.hxml,--no-output]
Defines <...>
Using signature 919e086b300264bdd80df09c4d0867c1
Display position: ?: -1--1
...
Stats = 7397 files, 7028 classes, 11745 methods, 119 macros
Time spent : 10.950s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":2,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":3,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":4,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":5,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":6,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":7,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.002s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":8,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":9,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":10,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":11,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":12,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":13,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.000s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":14,"method":"server/invalidate","params":{"file":"/xxxx/yyyy/src/zzz.hx"}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.001s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":15,"method":"display/completion","params":{"file":"/xxxx/yyyy/src/zzz.hx","contents":"<...>","offset":61553,"wasAutoTriggered":false}}]
Defines <...>
Using signature 919e086b300264bdd80df09c4d0867c1
Display position: /xxxx/yyyy/src/zzz.hx: 61553-61553
 3, js: parsed /xxxx/yyyy/src/zzz.hx (not cached, is display file)
Stats = 63 files, 6573 classes, 758 methods, 26 macros
Time spent : 5.956s
Processing Arguments [--cwd,/xxxx/yyyy,complete.hxml,-D,display-details,--no-output,--times,-D,macro-times,--display,{"jsonrpc":"2.0","id":16,"method":"display/completionItem/resolve","params":{"index":0}}]
Stats = 0 files, 0 classes, 0 methods, 0 macros
Time spent : 0.004s

This stands out to me. Are you using a custom HXML for code completion? It’s recommended to use the official Lime extension.

Considering there’s no “reusing” in your output at all, I wonder if anything has been cached at all.

Oh, that actually doesn’t seem to bad dependency-wise. I guess “large project” is mostly referring to the project’s code itself?

In a Hello World Flixel project, completion only takes ~50 ms for me. Can you reproduce these issues in a pure Lime / OpenFL / Starling project? (if using the Lime extension doesn’t help)

Thanks for your recommendation!
I installed lime extension and retried the completion. But this issue was not resolved…

Stats = 1 files, 6567 classes, 757 methods, 26 macros
Time spent : 8.529s

This issue dose not reproduce in a pure OpenFL project.

I’m trying to create a project that reproduces this issue. If I succeed in reproducing, I will post here again.

I create a reproduction code similar to the our actual project.
In this reproduction code, completion takes about 2 seconds.

Please check out this project.

Open this in VS Code.
Next, input “param.” and ctrl + space.

The completion is pretty fast here for me. The verbose output reports ~0.125s for a display/completion request.

Actually, after opening (without modifying) one of these files I can reproduce the slowdown, seeing a ton of messages like skipping generated.page199.Parameter49(generated.page199.Parameter26) in the verbose log.

1 Like

Oh, really? It seems that the way I conveyed it was not good.

  1. Input “param.” and CTRL + SPACE.
  2. Wait for displaying completion list.
  3. Cancel completion.
  4. Input BACKSPACE 1-time.
  5. Input “.” and CTRL + SPACE.
  6. Wait for displaying completion list. ← about 2sec

I repeated the steps 4-6 and tried the completion.

I retried this issue. This operation was incomplete.

Please input “this.” and CTRL + SPACE here.

If you do input completion once in Parameter0 class, this issue will be reproduced again in Logic.

1 Like

Thanks for the test project! I can definitely reproduce several issues here, starting with completion seemingly not auto-triggering… (seems an explicit Ctrl+Space is needed).

I’m not yet sure what’s causing the problem(s).

In this project, auto-completion is disabled. If you want to enable it, please comment out this part.

Oh, I hadn’t seen that. Well, that explains that part at least. :slight_smile:

So, I think there’s at least two issues here, one regarding invalidation and one regarding having a ton of files in a project.

The test project has a shitton of “generated” files (11304 files are being parsed in total according to readClassPaths). Toplevel completion is of course not too happy about this, since it will show every single type and package.

Is this similar to your actual project? Are all these types macro-generated there, or actual source files? Do you care about these things showing up in toplevel completion?

I think one thing we should certainly do is enhance the haxe.exclude setting a bit - this already allows hiding specific packages from completion, but this happens client-side and is thus not too helpful performance wise, it’s mainly for less crowded completion results atm. We should send the list of filtered packages to Haxe so it doesn’t send these to the language server to begin with.

Thank you for your looing into this issue.

Many actual source files exist. These are not macro-generated types.

There is almost no problem with not showing up these types in top-level completion.

It’s very good to me!

FWIW, I implemented the change I mentioned, there’s screenshots showing the performance improvement here. This doesn’t help with your main issue regarding invalidation though, that still has to be looked into…

1 Like

Thanks, Gama11.
I tried the dev-version haxe4 and vshaxe. There was no problem in the reproduced project, but it was not resolved in actual project…

I guess that this issue occurred because of compiler service compilations all dependency trees at the time of completion. I think that it can be solved if the Haxe compiler can cache the signatures of descendant nodes.