This document explains how to use the backend services in order to write a client.
If you read this documentation it means you are ready to implement a client, or more generally you want to use the backend services.
First, we will recap you how the communication with the backend works.
Then, after knowing how to communicate, we will tell you what to communicate, what kind of information you can request and receive.
The backend runs as a server: therefore it uses network concepts.
To be able to use a server, a few information is needed:
- network address
- protocol(s) used, and what is built on top
For now, the backend is served on localhost, and listening to connections on port 3000.
It uses the HTTP protocol.
On top of it is implemented a RPC protocol, using JSON.
However, there are other small services available through pure HTTP, please refer to the main documentation for more details on available routes.
RPC stands for Remote Procedure Call, and JSON is the famous JavaScript Object Notation format used for serialization.
RPC requests go through the route /rpc
and use the POST HTTP method.
RPC is used to expose the API of a system, in a standard way (system agnostic). Like this, as long as there is a way to communicate the RPC request, the API of any system is accessible for any other system, provided that both systems implement the protocol.
JSON is just the way to transport the information used for RPC.
As RPC is used to access an API, here are the information to be provided for a RPC request:
module
: the name of the module containing the property to access or method to call. Available modules are discussed below.method
: the name of the property to access or method to call inside the previous moduleargument
: a single object used to pass arguments
Example of a RPC request:
POST /rpc HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"module": "editor",
"method": "init",
"argument": {
"mode": "html"
}
}
Now that you know how to communicate with the backend, it's time to know how to use it for the application it serves, that is providing source code edition services!
The first step is to start the server, but this is beyond the scope of this article. We consider a server is running on http://localhost:3000.
However you can check the existence of a server using a ping method: http://localhost:3000/ping.
If you want to be sure however that this is not another server also responding a success status code on this path, you can use the identification pair system: on http://localhost:3000/80d007698d534c3d9355667f462af2b0 it should send e531ebf04fad4e17b890c0ac72789956
.
For now the only available module is editor
. So all your RPC request should use the following base JSON:
{
"module": "editor",
...
}
To be able to use the backend services for a document, you need to tell about it.
For that, register this document in the backend by initiating a session. Example for an empty HTML document:
{
"module": "editor",
"method": "init",
"argument": {
"mode": "html"
}
}
or with initial content, and custom properties:
{
"module": "editor",
"method": "init",
"argument": {
"mode": "html",
"source": "<html></html>",
"properties": {
"filename": "index.html",
...
}
}
}
You will receive the token for the document, a unique id to keep!! You will provide it for future requests for this document. Like this:
{
"guid": 0
}
While the document is updated in your client, you should tell the backend about changes you made inside. This way it will update the models it uses behind for other services.
Imagine you simply inserted a div
inside the html
node (<html><div></div></html>
):
{
"module": "editor",
"method": "exec",
"argument": {
"guid": "0",
"svc": "update",
"arg": {
"src": "<div></div>",
"start": 6
}
}
}
or you replaced the node, by another (<head></head>
instead of <html></html>
):
{
"module": "editor",
"method": "exec",
"argument": {
"guid": "0",
"svc": "update",
"arg": {
"src": "<div></div>",
"start": 0,
"end": 13
}
}
}
Other example just for convenience, you removed a portion of text:
{
"module": "editor",
"method": "exec",
"argument": {
"guid": "0",
"svc": "update",
"arg": {
"src": "",
"start": 0,
"end": 13
}
}
}
You can see that everything this can be generelized to a replace, with the end offset equal to the start offset of omitted.
Later on, we will implement more advanced features like moving text:
{
"module": "editor",
"method": "exec",
"argument": {
"guid": "0",
"svc": "update",
"arg": {
"start": 0,
"end": 6,
"newStart": 6
}
}
}
that would represents the change from <html></html>
to </html><html>
.
For highlighting, you will first need to use a stylesheet, that associates ids (tokens, elements, types, styles, call it whatever you want) to text presentation data. For that, send:
{
"module": "editor",
"method": "exec",
"argument": {
"id": "0",
"svc": "stylesheet"
}
}
It returns a stylesheet for the mode the corresponding document uses. It looks like this:
{
"styles": {
"comment": {
"color": {"r": 0, "g": 255, "b": 0},
"font": {"italic": true}
},
"string": {
"color": {"r": 255, "g": 0, "b": 0},
"font": {}
},
...
},
"default": {
"color": {"r": 255, "g": 255, "b": 255},
"background": {"r": 0, "g": 0, "b": 0},
"font": {
"family": "Consolas",
"height": 12,
"bold": false,
"italic": false,
"strike": false,
"underline": false
}
}
}
Then, to get highlighting data for the whole document:
{
"module": "editor",
"method": "exec",
"argument": {
"id": "0",
"svc": "highlight"
}
}
or a specific region:
{
"module": "editor",
"method": "exec",
"argument": {
"id": "0",
"svc": "highlight",
"arg": {
"start": 5,
"end": 10
}
}
}
The result being like this (for <html></html>
here):
{
"ranges": [
{
"start": 0,
"end": 6,
"style": "opening"
},
{
"start": 6,
"end": 13,
"style": "closing"
}
]
}
This is a list of ranges associated to the styles defined in the stylesheet.
It's up to you to make the link between ranges and actual text presentation data!
Notice that in case no style is available for a given range, you will have to use the default style specified in the stylesheet.
It is strongly advised that you cache the stylesheet instead of requesting it everytime - indeed our design of separating the two requests has been made for performance gain. However, in case the stylesheet has changed between two highlightings, an additional flag will be added to the results of the second highlighting request:
{
"ranges": [...],
"stylesheetChanged": true
}
Then you should request the stylesheet again before applying the new highlighting, and depending on your implementation on the client side, maybe you will have to ask for highlighting data for the whole document too.
You are know familiar with the concept of communicating with the backend.
Every other service are implemented the same way. To execute a service on an already registered document, you need to provide the basic JSON:
{
"module": "editor",
"method": "exec",
"argument": {
"id": ...,
"svc": ...,
"arg": ...
}
}
taking care of passing the proper id
for the document.
Just fill in the name of the service you want in svc
, and pass it arguments if necessary in arg
. Arguments change between services, some don't even take any for now.
Usually services have in common that they give information about the source code, or any portion of it (except things like the stylesheet for instance, which is a special case). Indeed a document is just a full text, combination of smaller text parts, until you reach the unit of a text: the character.
Thus, to simplify, these services always work on ranges of text. The thing is that you can avoid defining explicitely this range.
Here is how to define a range:
{
"start": 0,
"end": 13
}
Note that you can also pass a length instead of an offset, in which case you will need to pass at least an offset, or if none is passed the defaukt value for the start offset will be considered. In this case, proper offsets will be computed, and then the rules below apply normally.
Examples:
{"start": 5, "length": 5}
is
{"start": 5, "end": 10}
Also:
{"length": 10}
is
{"start": 0, "end": 10}
And in this case:
{"start": 5, "end": 10, "length": 15}
is
{"start": 5, "end": 10}
because length
is omitted.
And here are the rules:
- offsets are 0-based
- an offset is equivalent to the caret position is the editor and matches the character on its right
- the
start
offset is inclusive while theend
offset is exlusive start
defaults to0
, that is the beginning of the documentend
defaults to the end of the document, that is the length of the source text- if an offset is out of the boundaries of the document, it is cropped. So negative values will be transformed to
0
, and values further than the length of the document it will be changed to this length - if the
end
offset is inferior to thestart
one, it will be made equal tostart
So now, here are some examples:
- whole document:
{}
,{"start": 0}
,{"end": Infinity}
,{"start": 0, "end": Infinity}
(Infinity
is just a simple way to explicitely tell we want everything) - position:
{"start": 5, "end": 5}
(start == end
) - simple range:
{"start": 5, "end": 10}
- from beginning until ...:
{"end": 10}
- from ... until the end
{"start": 5}
Last but not least: it is possible that the service you're asking cannot work with the range you specified, because it is too small. In this case it would find the immediately bigger range it can work with, and consider you passed this. An additional property is added to the response in this case:
{
...
"computedRange": {
"start": 5,
"end": 10
}
}
This list is not exhaustive:
init
: special service not executed withexec
(not document related but documentS related)update
: to update changes made to a documentparse
: returns a full AST, for development purposes or more advanced processings on client-sidestylesheet
: used for the highlighting service, returns an assoiaction of ids and text styleshighlight
: returns a list of ranges associated to styles defined in the stylesheetoutline
: returns a summary of the code in the form of a treefold
: returns foldable rangesvalidate
: to comecomplete
: to come- ...
The ability for a client to configure services globally or specifically, in order to adapt them to the client.
For instance, there might be a clinet-level configuration to ask the services to return the range given in input or not: this could help for automatically inferred ranges.