TypeFox / monaco-languageclient

Repo hosts npm packages for monaco-languageclient, vscode-ws-jsonrpc, monaco-editor-wrapper, @typefox/monaco-editor-react and monaco-languageclient-examples
https://www.npmjs.com/package/monaco-languageclient
MIT License
1.06k stars 177 forks source link

Error: Unable to read file #765

Open shivam6543 opened 1 week ago

shivam6543 commented 1 week ago

Hello team,

When I try to use Peek References feature to see all the references of the function declared in the code but it throws the below error message:

Unable to read file '/workspace/file.java' (Error: Unable to resolve nonexistent file '/workspace/file.java')

I’m using monaco-languageclient@8.7.0 and monaco-vscode-api@7.0.7.

Code:

import * as monaco from 'monaco-editor';
import { initServices } from 'monaco-languageclient/vscode/services';
import { MonacoLanguageClient } from 'monaco-languageclient';
import { WebSocketMessageReader, WebSocketMessageWriter, toSocket } from 'vscode-ws-jsonrpc';
import { CloseAction, ErrorAction, MessageTransports } from 'vscode-languageclient';

let editor = null;
let languageClient = null;
let webSocket = null;

const startClient = async (value, language, fileUri) => {
  await initServices({});

  editor = monaco.editor.create(editorNode, {
    minimap: {
      enabled: false,
    },
    theme: "vs-dark",
  });

  let editorModel = monaco.editor.createModel(
    value,
    language,
    monaco.Uri.parse(fileUri)
  );

  editor.setModel(editorModel);

  webSocket = initLspConnection(`ws://localhost:8080/${language}`);
};

const initLspConnection = (url, language): WebSocket => {
  webSocket = new WebSocket(url);

  webSocket.onopen = async () => {
    const socket = toSocket(webSocket);
    const reader = new WebSocketMessageReader(socket);
    const writer = new WebSocketMessageWriter(socket);
    languageClient = createLanguageClient({
      reader,
      writer,
    }, language);

    await languageClient.start();

    reader.onClose(async () => await languageClient.stop());
  };
  return webSocket;
};

const createLanguageClient = (transports, language) => {
  return new MonacoLanguageClient({
    name: "Sample Language Client",
    clientOptions: {
      workspaceFolder,
      documentSelector: [language],
      errorHandler: {
        error: () => ({ action: ErrorAction.Continue }),
        closed: () => ({ action: CloseAction.DoNotRestart }),
      },
    },
    connectionProvider: {
      get: () => {
        return Promise.resolve(transports);
      },
    },
  });
};

// calling this on language change
const onChange = async (language, fileUri, value) => {
  await dispose();

  let editorModel = monaco.editor.createModel(
    value,
    language,
    monaco.Uri.file(fileUri)
  );

  editor.setModel(editorModel);

  webSocket = initLspConnection(`ws://localhost:8080/${language}`);
};

const dispose = async () => {
  if (languageClient) {
    await languageClient.dispose();
    languageClient = null;
  }

  if (webSocket) {
    webSocket.close();
    webSocket = null;
  }
};

startClient("", 'java', myFilePath)

can you please tell me how can I enable the peek references feature? It works in official monaco editor by default but not with the monaco-vscode-api.

CGNonofr commented 1 week ago

Are the references in the same file?

Anyway, the issue is that you use the monaco.editor.createModel syntax to create the model, which create a standalone model not "linked" to a file on a virtual filesystem.

You better create the file on the virtualfilesystem, then use the createModelReference instead

If you want to find references in other files, every files should exist on the memory filesystem

shivam6543 commented 1 week ago

@CGNonofr yes, the references are in the same file. In the official monaco editor package, this feature works by using creatModel syntax so I thought it will work here as well.

shivam6543 commented 1 week ago

In my setup, it’s quite complicated to use async createModelReference that’s why I’m using createModel. Is there any way to make it work using createModel?

CGNonofr commented 1 week ago

If you want to full explanation:

Unfortunately, the model-service override is flagged as required here and I don't think you can not be using it (@kaisalmen do you confirm)?

shivam6543 commented 1 week ago

@CGNonofr Thanks for your explanation!

I have some more questions around createModelReference usage.

When you say 'virtual filesystem’ does that mean the file should exist on the server?

What about languages that don’t require a file to exist on the server, for example, HTML, which provides intellisense using a web worker rather than via LSP? And what if, in some cases, I don’t want to use LSP, just want to use what monaco-editor provides by default and in that case, no file will exist on server. should I still use createModelReference? What should I do in this case?

can you please help me understand when should I use createModelReference and when should I use createModel?

One more thing: why is it not recommended to use second param of createModelReference?

CGNonofr commented 1 week ago

When you say 'virtual filesystem’ does that mean the file should exist on the server?

What I mean by virtual filesystem is the fileSystemProvider registered in the file service. In most cases and by default, it uses a RegisteredFileSystemProvider that is empty by default and allows to registered some static files in it (with content and save callback)

By if you use a remote Language Server, it's your responsability to make sure the virtual filesystem client-side is synchronized with the filesystem accessible to the remote language server. It's the tricky part, and there is multiple ways of achieving it depending on your needs. You can for instance replace the default filesystem implementation by one of yours

What about languages that don’t require a file to exist on the server, for example, HTML, which provides intellisense using a web worker rather than via LSP?

I'm not sure what you are talking about: the VSCode html extension or the monaco worker

The HTML VSCode extension is actually using LSP, the server just runs in a webworker and the web extension makes sure the worker server has access to the VSCode virtual filesystem

The monaco worker does pretty much the same, except it relies on the list of open models using the monaco-editor api instead of the vscode virtual filesystem

And what if, in some cases, I don’t want to use LSP, just want to use what monaco-editor provides by default and in that case, no file will exist on server. should I still use createModelReference? What should I do in this case?

The monaco intellisense workers are only able to see open models, which can be fine.

The issue with createModel is you can't call it twice on the same uri, so if you create your model this way, then the intellisense ask for a references on that model, it will try to create that model once again and will crash.

As said before, the standalone implementation of the textModelService works well with that, because when a references is created, it just return an immortal references to the existing model, or throw an error

So you either need to keep the standalone textModelService, or switch to createModelReference. Keep in mind that the first one won't allow to open files not already open

I'm not sure what can prevent you from using createModelReference. I guess that's because you're storing the model in your state somewhere. The alternative is to never store models, but only the file uris, and call createModelReference only to feed the editor or play with it a bit, then release it when not required anymore