2

I want to test the language server protocoll in a simple self written text editor. But I am not quite sure if I understand correctly how to write to the server and read from it. I want to do this in C++.

For testing purpose I use Qt in this example. But if you use another library this is okay too. As a server I installed ccls (it is working as I tested it with atom).

So this was my generel idea: 1. Start server as a process 2. Define a json file for the initialization according to the specification 3. Convert it into a String and sent it to the client 3. Wait for a response (Which should be an InitializeResult)

#include <QCoreApplication>
#include <QProcess>
#include <iostream>
#include <QFile>

int main(int argc, char* argv[]) {
  QCoreApplication app(argc, argv);

  QFile file {"src/initializeRequest.json"};
  file.open(QIODevice::ReadOnly);

  QProcess* myProcess = new QProcess(&app);
  myProcess->start("ccls", QStringList {});

  std::cout << myProcess->write(file.readAll()) << '\n';
  std::cout << myProcess->readAll().toStdString();
  file.close();
  return app.exec();
}

But actually I am not even sure if those messages (didOpen,initialzeRequest etc.) are really sent as files. According to the language server protocoll website they are interfaces descirbing a json file ... but I didn't find anything about how they are sent

So I would appreciate if any can tell me if I am on the right track (with my attempt to send actual files) and if anyone could show me the most simple communication to the server, so I actually get a response (to see if it works).

Leon0402
  • 160
  • 1
  • 9
  • 2
    Read fully through the documentation, including overview (https://microsoft.github.io/language-server-protocol/overview) and at least parts of the specification (https://microsoft.github.io/language-server-protocol/specification). You can't just simply "send a file" - understand the protocol first. – Felix Jan 20 '19 at 13:51
  • Thank you for the links. Of course, I took a look at the overview and the specifications. But doing it again, I realized I skipped some quite important parts (for the start). So I know understand that Microsoft defined for requests etc. typescript interfaces, which represent the json file (with the header, method, params etc.). And I first have to sent an Initalize request and if this is sucessfull I will get a response (so this is the minimal thing I have to do). I'm still not sure how this json is sent. Is it the way I did it: I have an initialize.json file and convert it into a string? – Leon0402 Jan 20 '19 at 14:15

1 Answers1

1

I experimented a lot and created a minimal working example. In this example I send an initialize request to ccls (which is a C++ language server) and read the response. I used QProcess for starting the server (but of course one can use something different) and rapidjson (https://github.com/Tencent/rapidjson) for the construction of the json file.

#include <QCoreApplication>
#include <QProcess>
#include <iostream>
#include <sstream>

#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

int main(int argc, char* argv[]) {
  QCoreApplication app {argc, argv};

  //Start server as a QProcess
  QProcess* server = new QProcess {&app};
  server->start("ccls", QStringList {"-log-file=/tmp/ccls2.log", "-init={}"});
  server->waitForStarted(-1);
  std::cout << "Server started" << '\n';

  //Construct json Request
  rapidjson::StringBuffer output {};
  rapidjson::Writer<rapidjson::StringBuffer> writer {output};

  writer.StartObject();
  writer.Key("jsonrpc");
  writer.String("2.0");
  writer.Key("id");
  writer.Int(0);
  writer.Key("method");
  writer.String("initialize");
  writer.Key("params");
  writer.StartObject();
  writer.Key("processId");
  writer.Int(0);
  writer.Key("rootUri");
  writer.String("home");
  writer.Key("capabilities");
  writer.StartObject();
  writer.EndObject();
  writer.EndObject();
  writer.EndObject();

  std::string content = output.GetString();
  std::ostringstream oss;
  oss << "Content-Length: " << content.length() << "\r\n" << "\r\n";
  std::string header = oss.str();

  std::cout << header << content << '\n';

  //Send request to server
  server->write(header.c_str());
  server->write(content.c_str());

  //Wait for response and read it
  server->waitForReadyRead(-1);
  std::cout << "Server has sent response" << '\n';
  std::cout << server->readAll().toStdString() << '\n';

  return app.exec();
}

And the output:

Server started
Content-Length: 106

{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":0,"rootUri":"home","capabilities":{}}}
Server has sent response
Content-Length: 1046

{"jsonrpc":"2.0","id":0,"result":{"capabilities":{"textDocumentSync":{"openClose":true,"change":2,"willSave":false,"willSaveWaitUntil":false,"
save":{"includeText":false}},"hoverProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":",">","#","<","\"",
"/"]},"signatureHelpProvider":{"triggerCharacters":["(",","]},"definitionProvider":true,"implementationProvider":true,"typeDefinitionProvider"
:true,"referencesProvider":true,"documentHighlightProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":true,"codeActionProvi
der":{"codeActionKinds":["quickfix"]},"codeLensProvider":{"resolveProvider":false},"documentFormattingProvider":true,"documentRangeFormattingP
rovider":true,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"}","moreTriggerCharacter":[]},"renameProvider":true,"documentLinkPr
ovider":{"resolveProvider":true},"foldingRangeProvider":true,"executeCommandProvider":{"commands":["ccls.xref"]},"workspace":{"workspaceFolder
s":{"supported":true,"changeNotifications":true}}}}}

As I said I wanted to show everyone who has the same question a minimal working example. Of course it makes sense to create structs for Request etc.

Leon0402
  • 160
  • 1
  • 9