eclipse-theia / theia

Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.
http://theia-ide.org
Eclipse Public License 2.0
20.1k stars 2.5k forks source link

Manually start language server and send notification or request #2419

Closed jeanlucburot closed 6 years ago

jeanlucburot commented 6 years ago

Is there a method to manually start a language server in Theia? I want to be able to send notifications or requests to the language server (i.e. the Java LSP) like textDocument/didOpen, textDocument/didChange or workspace/didChangeWorkspaceFolders.

Best case scenario would be something like this:

const javaLs: LanguageServer = this.someLsService.getLanguageServer("java");
const response = javaLs.textDocumentDidOpen("/path/to/my/file");
akosyakov commented 6 years ago

Could you elaborate why do you want it?

You can create a new model to send textDocument/didOpen and change it to send textDocument/didChange. You can change a file to trigger workspace/didChangeWorkspaceFolders.

jeanlucburot commented 6 years ago

The above suggested notifications and requests (textDocument/didOpen, textDocument/didChange, workspace/didChangeWorkspaceFolders) are just examples. I created a custom language server with LSP4J and have integrated custom notifications and requests into it.

Now I need to be able to trigger these custom requests and notifications in Theia, preferably with a button click. Hence, I need a way to declare the custom requests and notifications, and then I need a way to access the results received from the language server after having sent some request.

What classes/interfaces must I create/implement/extend to send and receive those mentioned requests and notifications? You said to create a model. What model do you mean exactly?

akosyakov commented 6 years ago

I see, yes generally you can do it, e.g. look how java triggers the organize import command: https://github.com/theia-ide/theia/blob/e445a4cd7b09c4e68fa07bd393af12734267ff80/packages/java/src/browser/java-commands.ts#L76-L82

jeanlucburot commented 6 years ago

Sounds like the a possible way to go. I tried it out, but fail to inject the JavaClientContribution using Inversify. The code compiles, but when running Theia, the browser gets stuck in the loading screen.

The console error is below the below code I wrote.

I created the file my-package-widget.tsx:

import { JavaClientContribution } from "@theia/java/lib/browser";
import { ILanguageClient } from "@theia/languages/lib/common";

@injectable()
export class MyPackageWidget extends ReactWidget {
    @inject(JavaClientContribution)
    protected readonly javaClientContribution: JavaClientContribution;

    private onButtonLanguageServerRequestClicked() {
        const clientPromise: Promise<ILanguageClient> = this.javaClientContribution.languageClient;
        clientPromise.then(res => {
            this.messageService.info("Successfully connected to the Java language server.");
        }).catch(err => {
            this.messageService.error("Error connecting to the Java language server. " + err.message);
        });
    }
}

Error in the browser console:

Failed to start the frontend application.

  | ./src-gen/frontend/index.js.module.exports.Promise.resolve.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.then.catch.reason | @ | index.js:69
-- | -- | -- | --
  | Promise.catch (async) |   |  
  | ./src-gen/frontend/index.js | @ | index.js:68
  | __webpack_require__ | @ | bootstrap:63
  | ../../node_modules/@phosphor/algorithm/lib/array.js.Object.defineProperty.value | @ | bootstrap:196
  | (anonymous)

ReferenceError: monaco is not defined
    at Object.../../packages/java/lib/browser/monaco-contribution/index.js (index.ts:20)
    at __webpack_require__ (bootstrap:63)
    at Object.../../packages/java/lib/browser/java-frontend-module.js (java-frontend-module.ts:31)
    at __webpack_require__ (bootstrap:63)
    at Object.../../packages/java/lib/browser/index.js (index.ts:22)
    at __webpack_require__ (bootstrap:63)
    at Object.../../packages/my-package/lib/browser/my-package-widget.js (my-package-widget.tsx:17)
    at __webpack_require__ (bootstrap:63)
    at Object.../../packages/my-package/lib/browser/my-package-frontend-module.js (my-package-frontend-module.ts:10)
    at __webpack_require__ (bootstrap:63)

Any idea how I can access languageClient? I guess injecting from one package to another is done differently? Is there a global registry I can access to get the language server from another package?

akosyakov commented 6 years ago

Are you sure that @theia/monaco package is installed? Could you share package.json for your extension and application?

jeanlucburot commented 6 years ago

Ok, one question remains considering starting and stopping the language server. The current status I have is that the first thing to do to access a language server is to inject access to it.

@inject(JavaClientContribution) protected readonly javaClientContribution: JavaClientContribution

The second thing is to get client access to the language server, done by waiting for an ILanguageClient object.

const client: ILanguageClient = await this.javaClientContribution.languageClient;

After that requests can be sent from the client to the language server, i.e. some completion request.

const result: CompletionResponse = await client.sendRequest<CompletionResponse>("textDocument/completion", {
    textDocument: {
        uri: uri
    },
    position: {
        line: 0,
        character: 0
    }
});

If though the server has not been yet started, the above code gets stuck at requesting the client object. To manually start the language server I found an activate() method, that seems to do what I would expect it to. This has to be executed before creating the client variable.

this.javaClientContribution.activate();

I could not find any method though to shutdown the language server. And I checked the process that is created for it. It remains open until I close the entire Theia IDE. Depending on the size of the language server this can eat up a lot of resources. Is there any way I can shutdown the language server again? I'm looking for something some counterpart of activate(), i.e. something like

this.javaClientContribution.deactivate();
akosyakov commented 6 years ago

One should not activate or deactivate a language client manually. It is the responsibility of the language contribution to decide by customizing waitForActivation: https://github.com/theia-ide/theia/blob/b81d6b1871a00f60fe6350d5e1737b4a73697a0d/packages/languages/src/browser/language-client-contribution.ts#L63-L86.

jeanlucburot commented 6 years ago

Ok, I am not sure if I really understand this method the right way. What I need is independence of open text documents when communicating with the language server. So what I did is basically overridden the waitForActivation method by keeping everything as is, but uncommenting line 78 await p. This way, I assume` the language server does not wait for an open document to be available, which is exactly what I need. So far it works.

So did I understand it right?