Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Separate Export for LSP Services #1258

Merged
merged 5 commits into from Feb 13, 2024
Merged

Conversation

montymxb
Copy link
Contributor

@montymxb montymxb commented Nov 2, 2023

Closes #1166 by providing a separate langium/lsp export for LSP-related services for Langium.

Note, this is a breaking change planned for introduction later in v3.0.0.

Changes (not exhaustive):

  • Splits lsp related services into lsp-services & langium-lsp-module
  • Furrther split some existing services into default & lsp variants (configuration provider, langium document factory, workspace manager, to name a few)
  • Exposed related types like LangiumServicesWithLSP and LangiumSharedServicesWithLSP to make it pretty clear how to extend existing applications with the new type signatures
  • Updates existing example languages to incorporate the new LSP module (in conjunction with the default one) to work correctly
  • More or less leaves everything in 'grammar' that is tied to /lsp alone
  • TextDocument(s) were pulled out into the LSP, but we may want to put this back, I'm open for thoughts here
  • updates impacted tests
  • inject can inject more

Open for refactors and changes based on feedback, and can go from there.

createServicesForGrammarWithLSP is marked with a TODO that is a effectively an RFC. Looking for feedback to see if I should generalize the approach a bit more to work with createServicesForGrammar, or leave it as is.

@montymxb montymxb added this to the v3.0.0 milestone Nov 2, 2023
@spoenemann
Copy link
Contributor

We need to eliminate the internal dependencies to the lsp subfolder. This is only possible by splitting default-module.ts.

@msujew msujew added the LSP Language Server Protocol integration label Nov 2, 2023
@montymxb montymxb changed the title Add Separate Export for LSP Services WIP: Add Separate Export for LSP Services Nov 2, 2023
@montymxb montymxb force-pushed the montymxb/esm-lsp-exports branch 2 times, most recently from 76f4875 to d04fdf8 Compare November 17, 2023 16:10
@montymxb montymxb changed the title WIP: Add Separate Export for LSP Services Add Separate Export for LSP Services Nov 17, 2023
@montymxb montymxb marked this pull request as ready for review November 17, 2023 17:27
Copy link
Contributor

@spoenemann spoenemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this first spike!

I'm thinking about a different approach to structure the services and modules:

  • Introduce LangiumCoreServices and LangiumLspServices for the split, incl. respective shared services and default modules
  • Keep LangiumServices and LangiumSharedServices intact, i.e. the union of core and LSP (implying that these need to be defined in the lsp subfolder)

Rationale:

  • Reduce the impact of the change in terms of breaking existing code (just change the import)
  • Splitting core and LSP services is not something that many users will do, but rather a special feature for those using Langium as a library in the browser
  • We should keep things simple for the most common case, but make the more special cases possible

@msujew WDYT?

@montymxb
Copy link
Contributor Author

montymxb commented Nov 22, 2023

@spoenemann I'm on board with the core vs. lsp services 👍 . I was also wondering if we wanted to do something with an explicit 'core' set of services, I like that.

In addition retaining the structure of LangiumServices and LangiumSharedServices as being lsp-complete makes sense, given existing consumers of these services will generally expect them to be complete. I can roll that change in later this week.

@montymxb
Copy link
Contributor Author

Also renamed the DefaultServiceRegistry to CoreServiceRegistry, still not 100% sure on this given the 'core' registry is also used in the lsp + core case -- just with a different interface.

@montymxb
Copy link
Contributor Author

montymxb commented Nov 24, 2023

Just a note, there's a single test failure after the last rebase on top of the new ./generate export work. I'm not able to pin it down at this time, but the CI is a bit misleading here. There appears to be a separate error leading the reported failure regarding vite being unable to resolve some functions (or typeinfo), for example:

TypeError: vite_ssr_import_0.createDefaultSharedModule is not a function

Several other 'langium' imports in the domainmodel example seem to have a similar issue, but strangely not others like requirements or statemachine, which look like they should trigger the same issue. The plan is to keep looking into this more next week to resolve.

Copy link
Contributor

@sailingKieler sailingKieler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good progress @montymxb!
I have a few detail remarks/wishes and one major thing: The workspace initialization should not got into the LSP related part.

I think special attention need to be paid to the stuff in promise-util.ts that need to be separated, too, doesn't it?

examples/domainmodel/test/domainmodel-cli.test.ts Outdated Show resolved Hide resolved
packages/langium/src/service-registry.ts Outdated Show resolved Hide resolved
packages/langium/src/service-registry.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/langium-lsp-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/langium-lsp-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/langium-lsp-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/workspace/workspace-manager.ts Outdated Show resolved Hide resolved
@msujew
Copy link
Contributor

msujew commented Dec 1, 2023

@montymxb #1171 has been merged, feel free to rebase :)

@montymxb
Copy link
Contributor Author

montymxb commented Dec 4, 2023

CI looks good sans lint. Local linting & gitpod linting looks good despite this, so this action triggered lint is still outstanding for now.

Copy link
Contributor

@msujew msujew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, you've made good progress on this. We needsome better abstraction layers for some of these services, but otherwise this looks quite good.

packages/langium/src/service-registry.ts Outdated Show resolved Hide resolved
packages/langium/src/workspace/workspace-manager.ts Outdated Show resolved Hide resolved
packages/langium/test/lsp/fuzzy-matcher.test.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/workspace/workspace-manager.ts Outdated Show resolved Hide resolved
packages/langium/src/workspace/configuration.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/workspace/documents.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/workspace/documents.ts Outdated Show resolved Hide resolved
@montymxb
Copy link
Contributor Author

Thanks for the feedback @msujew . I'm catching up on some things right now, and then should be coming back to reply & update in a day or two.

Copy link
Contributor

@spoenemann spoenemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the amount of additional complexity that is necessary to separate LSP from Core, I am no longer convinced that the benefits of the separation justify its costs.

What's the use case for the separation? Using Langium in a browser app to parse and process text (without editor support), with a minimal bundle size. That's a rather specific case.

Can we offer an alternative solution for that use case? For example, a recommended bundling configuration where imports from vscode-languageserver are replaced by some mocks?

Let's discuss this in January when everyone's back from vacations.

Edit: I resolved my detail comments below – there's no need to address them before we clarify how to proceed in general.

examples/domainmodel/src/cli/cli-util.ts Outdated Show resolved Hide resolved
packages/langium-sprotty/src/default-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/default-lsp-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/definition-provider.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/lsp-services.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@sailingKieler sailingKieler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work @montymxb!!

I have some remarks to simplify some things, and I agree with Miro, that the LSP services should expect LangiumServices instead of LangiumCoreServices, but in general I'm very in favor of this contribution!

packages/langium/src/workspace/documents.ts Outdated Show resolved Hide resolved
packages/langium/src/workspace/workspace-manager.ts Outdated Show resolved Hide resolved
packages/langium/src/workspace/configuration.ts Outdated Show resolved Hide resolved
packages/langium/src/utils/cancellation-token.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/lsp-services.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/language-server.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/language-server.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/language-server.ts Outdated Show resolved Hide resolved
packages/langium/src/service-registry.ts Outdated Show resolved Hide resolved
@montymxb
Copy link
Contributor Author

Thanks for the feedback @sailingKieler . Added some notes back to what you've mentioned.

@sailingKieler
Copy link
Contributor

sailingKieler commented Jan 23, 2024

Hey @msujew,

I added another commit contributing packages/langium/src/utils/cancellation.ts just containing
export * from 'vscode-jsonrpc/lib/common/cancellation.js'; and updating all related imports.

I added it to utils/index.ts as a named export similar to the AstUtils to not pollute Langium's namespace further.

Technically, the results will be of the same size as with copying the code.
How likely is it that vscode-jsonrpc will also get node16-exports in near future? (like vscode-languageserver-types has, for example). Should I add an export of vscode-jsonrpc/lib/common/event.js, too?
What do you think about that?

@sidharthv96
Copy link
Contributor

@msujew yes, this is very crucial for us at Mermaid.

This is our current bundle breakdown.
image

And this is the breakdown of our parser package.
image

VSCode related packages make up around 1/3rd of the bundle.
With the ~175Kb reduction from #1171, and ~200Kb from this PR, it'll cut the size by more than 50%, which is huge for mermaid. 🚀

mermaid-js/mermaid#4734 (comment)

@sailingKieler
Copy link
Contributor

I did another refinement, now there's no import ... from 'vscode-jsonrpc'; in the code base anymore, except for
export * from 'vscode-jsonrpc/lib/common/cancellation.js'; in utils/cancellation.ts, as mentioned above.

I also double-checked on exporting Event (see above), but that doesn't bring any value IMO, so I discarded that.

I have one more commit in the pipeline replacing all type imports of vscode-languageserver by vscode-languageserver-types or vscode-languageserver-protocol, but that would be too much for this PR I think.

@msujew
Copy link
Contributor

msujew commented Jan 24, 2024

@sidharthv96 Thanks for the info! Good to know that we're on the right track on this.

@msujew
Copy link
Contributor

msujew commented Jan 24, 2024

@sailingKieler

How likely is it that vscode-jsonrpc will also get node16-exports in near future? (like vscode-languageserver-types has, for example). Should I add an export of vscode-jsonrpc/lib/common/event.js, too?

Good question - no idea. I assume at the same time that vscode switches over to ESM, which might take a while. I think the new approach is just fine.

@Lotes
Copy link
Contributor

Lotes commented Jan 24, 2024

Why was InitializableService reintroduced? Is it not better to register an event listener? Adding super types to make this happen is a solution. But I would prefer avoiding changes on the type hierarchy, since is already doable with listeners.

Or do I miss something?

@sailingKieler
Copy link
Contributor

sailingKieler commented Jan 24, 2024

Why was InitializableService reintroduced? Is it not better to register an event listener? Adding super types to make this happen is a solution. But I would prefer avoiding changes on the type hierarchy, since is already doable with listeners.

Or do I miss something?

As you can see in https://github.com/eclipse-langium/langium/pull/1258/files#diff-9ef9cc03a159c7e21b4350faa99ddd28f97e4f27c934a51508e14531e4f4987bL49-L58 the DefaultWorkspaceManager (a core service) accessed the LanguageServer -- an LSP service --, which is to be avoided.
Same was the case for the DefaultConfigurationProvider. Moreover, the latter relied on API of vscode-languageserver that is also to be avoided.

@sidharthv96
Copy link
Contributor

sidharthv96 commented Jan 25, 2024

Are there any migration docs in the works, that I can refer to and try to make changes in our parser?
Or an example of how we can get from a string to AST without LSP. I think the current examples rely on LSP.

@sailingKieler
Copy link
Contributor

Are there any migration docs in the works, that I can refer to and try to make changes in our parser? Or an example of how we can get from a string to AST without LSP. I think the current examples rely on LSP.

@sidharthv96 the changes of this will mainly require you to update/refactor imports within your custom code, and to re-generate the generated code with the langium cli.

In addition, to take advantage of this restructuring, you have to rework the preparations in your langium config module, precisely replace the calls of createDefault[Shared]Module by createDefault[Shared]CoreModule, and adapt the imports accordingly. That's basically it wrt. to this PR.

However, the changes of #1320 will most likely induce more manual migration effort.
For sure, there will be some migration guide.

Copy link
Contributor

@spoenemann spoenemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now in a really good shape, good work! 💪

The poll results don't suggest that this effort is needed: at the time of writing, none of the 26 participants selected "Parsing only". However, we have statements from @sidharthv96 (#1258 (comment)) and @jindong-zhannng (#1346 (comment)) confirming that there are at least some applications where bundle size is crucial. I see this as a sufficient sign that we can proceed, provided that the changes are not too disruptive for other users.

I think we found good solutions to avoid raising the overall complexity for "normal" Langium users. We still need to write a migration guide, but in most cases it's just about updating imports (which is necessary for other v3 changes, too).

@sailingKieler please have a look at my questions and remarks below. In addition, I found imports from 'vscode-languageserver' in the following source files, which should be changed to type imports from 'vscode-languageserver-types':

  • ast-utils.ts
  • cst-utils.ts
  • jsdoc.ts

packages/langium/src/default-module.ts Outdated Show resolved Hide resolved
packages/langium/src/lsp/language-server.ts Show resolved Hide resolved
packages/langium/src/validation/document-validator.ts Outdated Show resolved Hide resolved
packages/langium/src/validation/validation-registry.ts Outdated Show resolved Hide resolved
packages/langium/src/workspace/documents.ts Outdated Show resolved Hide resolved
…cuments.ts for convenience

* the overhead is just very few kilobytes
* changed all imports of 'TextDocument' to point to langium
…jsonrpc/lib/common/cancellation.js';"; updated related imports

aims at providing cancellation related types and symbols with smallest possible overhead while avoiding code clones
…dded linting rule preventing imports from 'vscode-jsonrpc'
* Everything else of that package (at the time contributing) is also defined
* in 'vscode-languageserver-protocol' or 'vscode-languageserver-types'.
*/
export { TextDocument } from 'vscode-languageserver-textdocument';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now limited the re-export of vscode-languageserver-textdocument to TextDocument, as duplicate definitions are accessible via import ... from 'vscode-languageserver-protocol' for all other definitions exported by vscode-languageserver-textdocument.

…-languageserver-types' in langium's core modules (non-lsp, non-grammar)
@@ -232,13 +233,13 @@ export function getDiagnosticRange<N extends AstNode>(info: DiagnosticInfo<N, st
export function toDiagnosticSeverity(severity: 'error' | 'warning' | 'info' | 'hint'): DiagnosticSeverity {
switch (severity) {
case 'error':
return DiagnosticSeverity.Error;
return 1; // according to vscode-languageserver-types/lib/esm/main.js#DiagnosticSeverity.Error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now replaced the constants like DiagnosticSeverity.Information from vscode-languageserver-types by their values, and left an info on their original definition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of this - users that don't want to include vscode-languageserver-types in their build, are now only allowed to use the numbers themselves (or have to redeclare the enum themselves). We should offer the enum ourselves.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this peace of code is only used to convert the Langium notion of severity being defined as 'error' | 'warning' | 'info' | 'hint' into the LSP notion while composing LSP diagnostics, right?
Our code is just converting the values according to the protocol definition, and therefore I don't see why Langium should export copies of those definitions.
Do you have concrete use cases in mind? Why should users care about those constants?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right I forgot. Alright, that's fine then 👍

Copy link
Contributor

@spoenemann spoenemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ship it! 🚀

@sailingKieler sailingKieler merged commit 9e0a475 into main Feb 13, 2024
4 checks passed
@sailingKieler sailingKieler deleted the montymxb/esm-lsp-exports branch February 13, 2024 14:47
export interface InitializableService {
initialize(params: InitializeParams): void
initialized(params: InitializedParams): MaybePromise<void>
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again, I'd rather remove this interface: we already have onInitialize and onInitialized events in the LanguageServer service, which are the recommended ways to hook into the LSP lifecycle. The interface is only used by two core framework classes which require to invert the dependency. The interface is not necessary because LanguageServer hard-codes the calls to these classes anyway.

Copy link
Contributor

@Yokozuna59 Yokozuna59 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, thanks for your work!

I'm currently facing an issue regarding the ServiceRegistry#getServices, see the comment below.


/**
* Retrieve the language-specific services for the given URI. In case only one language is
* registered, it may be used regardless of the URI format.
*/
getServices(uri: URI): LangiumServices;
getServices(uri: URI): LangiumCoreServices;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I access the LSP services if this method only returns LangiumCoreServices?

I'm currently trying to run two language servers in the same document, one of which is the Langium language server and the other not.
The Langium language server is the default one, and if the user typed some specific text, it would use the other language server suggestions.

Anyway, the approach I'm taking is to create two modes; according to mode, the language server would use either the language server or the other language server methods to suggest, but now I can't use the language server since I can't directly access the LSP services.

Here is an example:

export class LangiumMode implements LanguageMode {
  private getServices({ uri }: TextDocument): LangiumServices /* error in 3.0.0 */ { 
    return this.shared.ServiceRegistry.getServices(URI.parse(uri));
  }

  public doComplete(
    document: TextDocument,
    params: CompletionParams,
    cancelToken?: CancellationToken | undefined,
  ): MaybePromise<CompletionList | undefined> {
    return this.getServices(document).lsp.CompletionProvider?.getCompletion(
      this.textDocumentToLangiumDocument(document),
      params,
      cancelToken,
    );
  }
  
  // ...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me it worked to downcast

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Yokozuna59 A quick answer: Look #1166 (comment) and below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks @cdietrich @sailingKieler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LSP Language Server Protocol integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Export LSP services via dedicated ESM export
8 participants