Notification support
This commit is contained in:
parent
64db0526ad
commit
297c8f800e
7 changed files with 286 additions and 102 deletions
3
Makefile
3
Makefile
|
|
@ -17,7 +17,8 @@ INCLUDES= \
|
|||
SRCS= \
|
||||
libraries/cJSON/cJSON.c \
|
||||
src/json_rpc.c \
|
||||
src/main.c
|
||||
src/main.c \
|
||||
src/pawn_lsp.c
|
||||
|
||||
OBJS=$(addprefix $(BUILD_DIR)/,$(addsuffix .o,$(basename $(SRCS))))
|
||||
DEPS=$(addprefix $(BUILD_DIR)/,$(addsuffix .d,$(basename $(SRCS))))
|
||||
|
|
|
|||
170
src/json_rpc.c
170
src/json_rpc.c
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "cJSON/cJSON.h"
|
||||
#include "log.h"
|
||||
#include "lsp.h"
|
||||
#include "json_rpc.h"
|
||||
|
||||
static void _service_request(json_rpc_t *jrpc);
|
||||
|
|
@ -39,12 +40,22 @@ json_rpc_init(json_rpc_t *jrpc, int infd, int outfd)
|
|||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
json_rpc_add_method(json_rpc_t *jrpc, const char *method,
|
||||
int (*callback)(json_rpc_request_t *req, cJSON **response, void *arg),
|
||||
void
|
||||
json_rpc_set_request_handler(json_rpc_t *jrpc,
|
||||
int (*callback)(json_rpc_request_t *req, cJSON **resp, void *arg),
|
||||
void *arg)
|
||||
{
|
||||
return (-1);
|
||||
jrpc->req_handler.callback = callback;
|
||||
jrpc->req_handler.arg = arg;
|
||||
}
|
||||
|
||||
void
|
||||
json_rpc_set_notify_handler(json_rpc_t *jrpc,
|
||||
void (*callback)(json_rpc_request_t *req, void *arg),
|
||||
void *arg)
|
||||
{
|
||||
jrpc->notf_handler.callback = callback;
|
||||
jrpc->notf_handler.arg = arg;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -73,12 +84,6 @@ json_rpc_run(json_rpc_t *jrpc)
|
|||
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
free(jrpc->buf);
|
||||
for (size_t i = 0; i < jrpc->method_cnt; i++) {
|
||||
free(jrpc->methods[i].method);
|
||||
}
|
||||
free(jrpc->methods);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -89,68 +94,88 @@ json_rpc_cancel(json_rpc_t *jrpc)
|
|||
|
||||
|
||||
|
||||
static int
|
||||
_read_headers(int fd, char *buf, size_t len, size_t *content_len, char **content_type)
|
||||
{
|
||||
size_t p = 0;
|
||||
|
||||
(*content_len) = 0;
|
||||
(*content_type) = NULL;
|
||||
|
||||
while (1) {
|
||||
size_t rlen = _read_until(fd, &buf[p], len - p, '\n', 1000);
|
||||
|
||||
if ((rlen < 2) || (buf[p + rlen - 1] != '\n')) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* "Content-Length: <val>\r\n" */
|
||||
if ((rlen > 18) && (!memcmp(&buf[p], "Content-Length: ", 16))) {
|
||||
(*content_len) = strtoul(&buf[p + 16], NULL, 10);
|
||||
}
|
||||
/* "Content-Type: <val>\r\n" */
|
||||
else if ((rlen > 16) && (!memcmp(&buf[p], "Content-Type: ", 14))) {
|
||||
(*content_type) = &buf[p + 14];
|
||||
buf[p + rlen - 2] = '\0';
|
||||
}
|
||||
else if (rlen == 2) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
p += rlen;
|
||||
if (p >= len) {
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_service_request(json_rpc_t *jrpc)
|
||||
{
|
||||
char *buf = jrpc->buf;
|
||||
char *len_str;
|
||||
char *content;
|
||||
size_t p = 0;
|
||||
size_t ret;
|
||||
long content_len;
|
||||
char headers[1024] = { 0 };
|
||||
size_t content_length = 0;
|
||||
char *content_type = NULL;
|
||||
size_t rlen;
|
||||
json_rpc_request_t request;
|
||||
|
||||
/* read Content-Length: ...\r\n */
|
||||
ret = _read_bytes(jrpc->infd, &buf[p], 16, 1000);
|
||||
if ((ret != 16) || (memcmp(&buf[p], "Content-Length: ", 16))) {
|
||||
log_error("Failed to read request header\r\n");
|
||||
if (_read_headers(jrpc->infd, headers, sizeof(headers), &content_length, &content_type)) {
|
||||
log_error("Failed to read/parse headers\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p += ret;
|
||||
len_str = &buf[p];
|
||||
|
||||
ret = _read_until(jrpc->infd, &buf[p], jrpc->bufsz - p, '\n', 1000);
|
||||
p += ret;
|
||||
if ((ret == 0) || (buf[p-1] != '\n')) {
|
||||
log_error("Failed to read content length value\r\n");
|
||||
log_info("Request { length: %ld, type: %s }\r\n", content_length, content_type);
|
||||
if (content_length == 0) {
|
||||
log_error("Content-Length of 0\r\n");
|
||||
return;
|
||||
}
|
||||
else if (content_length > jrpc->bufsz) {
|
||||
log_error("Content length too long!\r\n");
|
||||
while (content_length > 0) {
|
||||
size_t rlen = content_length > jrpc->bufsz ? jrpc->bufsz : content_length;
|
||||
rlen = _read_bytes(jrpc->infd, jrpc->buf, rlen, 1000);
|
||||
content_length -= rlen;
|
||||
if (rlen == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* TODO error response */
|
||||
return;
|
||||
}
|
||||
|
||||
content_len = strtol(len_str, NULL, 10);
|
||||
if (content_len < 0) {
|
||||
log_warn("Invalid content length %ld\r\n", content_len);
|
||||
return;
|
||||
}
|
||||
if (content_len > (jrpc->bufsz - p - 3)) {
|
||||
log_warn("Content too long (%ld)\r\n", content_len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read empty CRLF */
|
||||
ret = _read_bytes(jrpc->infd, &buf[p], 2, 1000);
|
||||
if (ret != 2) {
|
||||
log_error("Failed to read blank line\r\n");
|
||||
return;
|
||||
}
|
||||
p += ret;
|
||||
|
||||
content = &buf[p];
|
||||
ret = _read_bytes(jrpc->infd, &buf[p], content_len, 1000);
|
||||
if (ret != content_len) {
|
||||
memset(jrpc->buf, 0, jrpc->bufsz);
|
||||
rlen = _read_bytes(jrpc->infd, jrpc->buf, content_length, 1000);
|
||||
if (rlen != content_length) {
|
||||
log_error("Failed to read all of content, exp %ld, got %ld\r\n",
|
||||
content_len, ret);
|
||||
content_length, rlen);
|
||||
return;
|
||||
}
|
||||
p += ret;
|
||||
buf[p] = '\0';
|
||||
|
||||
|
||||
|
||||
/* JSON parsing */
|
||||
request.request = cJSON_Parse(content);
|
||||
request.request = cJSON_Parse(jrpc->buf);
|
||||
if ((!request.request) || (!cJSON_IsObject(request.request))) {
|
||||
log_error("Failed to parse request %s\r\n", buf);
|
||||
log_error("Failed to parse request %s\r\n", jrpc->buf);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +189,9 @@ _service_request(json_rpc_t *jrpc)
|
|||
}
|
||||
|
||||
request.id = cJSON_GetObjectItem(request.request, "id");
|
||||
if ((!request.id) ||
|
||||
((!cJSON_IsNumber(request.id)) && (!cJSON_IsString(request.id)))) {
|
||||
if ((request.id != NULL) &&
|
||||
(!cJSON_IsNumber(request.id)) &&
|
||||
(!cJSON_IsString(request.id))) {
|
||||
log_error("Invalid ID format\r\n");
|
||||
return;
|
||||
}
|
||||
|
|
@ -177,19 +203,19 @@ _service_request(json_rpc_t *jrpc)
|
|||
}
|
||||
|
||||
request.params = cJSON_GetObjectItem(request.request, "params");
|
||||
if (request.params) {
|
||||
if ((!cJSON_IsArray(request.params)) && (!cJSON_IsObject(request.params))) {
|
||||
if ((request.params != NULL) &&
|
||||
(!cJSON_IsArray(request.params)) &&
|
||||
(!cJSON_IsObject(request.params))) {
|
||||
log_error("Invalid params format\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (request.id) {
|
||||
/* Request */
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON *val = NULL;
|
||||
cJSON *body = NULL;
|
||||
const char *resp_token = "error";
|
||||
json_rpc_method_t *method = NULL;
|
||||
|
||||
if (response == NULL) {
|
||||
cJSON_Delete(request.request);
|
||||
|
|
@ -207,24 +233,14 @@ _service_request(json_rpc_t *jrpc)
|
|||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < jrpc->method_cnt; i++) {
|
||||
if (!strcmp(jrpc->methods[i].method, request.method->valuestring)) {
|
||||
method = &jrpc->methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!method) {
|
||||
log_warn("No handler for method \"%s\"\r\n", request.method->valuestring);
|
||||
} else if (method->callback(&request, &body, method->arg) == 0) {
|
||||
resp_token = "response";
|
||||
|
||||
if (body == NULL) {
|
||||
log_error("No response body given for successful callback\r\n");
|
||||
if (cJSON_AddStringToObject(response, "jsonrpc", "2.0") == NULL) {
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(request.request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (jrpc->req_handler.callback(&request, &body, jrpc->req_handler.arg) == 0) {
|
||||
resp_token = "result";
|
||||
}
|
||||
|
||||
if (body) {
|
||||
|
|
@ -238,7 +254,7 @@ _service_request(json_rpc_t *jrpc)
|
|||
return;
|
||||
}
|
||||
|
||||
if ((cJSON_AddNumberToObject(body, "code", -32601) == NULL) ||
|
||||
if ((cJSON_AddNumberToObject(body, "code", LSP_ERR_MethodNotFound) == NULL) ||
|
||||
(cJSON_AddStringToObject(body, "message", "Not implemented.") == NULL)) {
|
||||
cJSON_Delete(body);
|
||||
cJSON_Delete(response);
|
||||
|
|
@ -269,8 +285,10 @@ _service_request(json_rpc_t *jrpc)
|
|||
free(resp_str);
|
||||
}
|
||||
|
||||
cJSON_Delete(body);
|
||||
cJSON_Delete(response);
|
||||
} else {
|
||||
/* Notify */
|
||||
jrpc->notf_handler.callback(&request, jrpc->notf_handler.arg);
|
||||
}
|
||||
|
||||
cJSON_Delete(request.request);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
typedef struct json_rpc_request_struct {
|
||||
/** Base request structure, guaranteed to be an object */
|
||||
cJSON *request;
|
||||
/** Reference to the value of the ID field, guaranteed to be a string or number */
|
||||
/** Reference to the value of the ID field, guaranteed to be a string, or
|
||||
* number for requests, and NULL for notifications. */
|
||||
cJSON *id;
|
||||
/** Reference to the value of the method field, guaranteed to be a string */
|
||||
cJSON *method;
|
||||
|
|
@ -18,26 +19,21 @@ typedef struct json_rpc_request_struct {
|
|||
cJSON *params;
|
||||
} json_rpc_request_t;
|
||||
|
||||
typedef struct json_rpc_method_struct {
|
||||
/** "method" field to match on */
|
||||
char *method;
|
||||
/** User arg to pass into callback */
|
||||
void *arg;
|
||||
/** Callback to handle the method - 0 return indicates to send back a
|
||||
* successful result, non-zero will treat the response object as a response
|
||||
* error object */
|
||||
int (*callback)(json_rpc_request_t *req, cJSON **response, void *arg);
|
||||
} json_rpc_method_t;
|
||||
|
||||
typedef struct json_rpc_struct {
|
||||
/** Socket to read requests from */
|
||||
int infd;
|
||||
/** Socket to write responses to */
|
||||
int outfd;
|
||||
/** List of methods added */
|
||||
json_rpc_method_t *methods;
|
||||
/** Number of methods added */
|
||||
size_t method_cnt;
|
||||
/** Registered request handler info */
|
||||
struct {
|
||||
int (*callback)(json_rpc_request_t *, cJSON **, void *);
|
||||
void *arg;
|
||||
} req_handler;
|
||||
/** Registered notification handler info */
|
||||
struct {
|
||||
void (*callback)(json_rpc_request_t *, void *);
|
||||
void *arg;
|
||||
} notf_handler;
|
||||
/** Main server thread */
|
||||
pthread_t thread;
|
||||
/** Allocated buffer for reading requests */
|
||||
|
|
@ -53,8 +49,12 @@ typedef struct json_rpc_struct {
|
|||
|
||||
int json_rpc_init(json_rpc_t *jrpc, int infd, int outfd);
|
||||
|
||||
int json_rpc_add_method(json_rpc_t *jrpc, const char *method,
|
||||
int (*callback)(json_rpc_request_t *req, cJSON **response, void *arg),
|
||||
void json_rpc_set_request_handler(json_rpc_t *jrpc,
|
||||
int (*callback)(json_rpc_request_t *req, cJSON **resp, void *arg),
|
||||
void *arg);
|
||||
|
||||
void json_rpc_set_notify_handler(json_rpc_t *jrpc,
|
||||
void (*callback)(json_rpc_request_t *req, void *arg),
|
||||
void *arg);
|
||||
|
||||
void json_rpc_run(json_rpc_t *jrpc);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
#ifndef _LSP_H_
|
||||
#define _LSP_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ */
|
||||
|
||||
typedef enum lsp_error_enum {
|
||||
|
|
|
|||
10
src/main.c
10
src/main.c
|
|
@ -1,14 +1,17 @@
|
|||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "json_rpc.h"
|
||||
#include "pawn_lsp.h"
|
||||
|
||||
void sighandler(int signal);
|
||||
|
||||
static json_rpc_t jrpc;
|
||||
static pawn_lsp_t pawn;
|
||||
|
||||
FILE *logger = NULL;
|
||||
|
||||
|
|
@ -42,11 +45,18 @@ main(int argc, char **argv)
|
|||
return (-1);
|
||||
}
|
||||
|
||||
if (pawn_lsp_init(&pawn, &jrpc, argv[1], &argv[2], argc - 2)) {
|
||||
log_error("Failed to register Pawn language server\r\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
json_rpc_run(&jrpc);
|
||||
pawn_lsp_deinit(&pawn);
|
||||
|
||||
if (logger) {
|
||||
fclose(logger);
|
||||
}
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_orig);
|
||||
return (0);
|
||||
}
|
||||
|
|
|
|||
132
src/pawn_lsp.c
Normal file
132
src/pawn_lsp.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cJSON/cJSON.h"
|
||||
#include "json_rpc.h"
|
||||
#include "log.h"
|
||||
#include "pawn_lsp.h"
|
||||
|
||||
#define PAWN_LSP_VESRION "v0.0.1"
|
||||
#define PAWN_LSP_NAME "pawn_lsp"
|
||||
|
||||
|
||||
static int _request_handler(json_rpc_request_t *req, cJSON **resp, void *arg);
|
||||
static void _notify_handler(json_rpc_request_t *req, void *arg);
|
||||
|
||||
static int _initialize(json_rpc_request_t *req, cJSON **resp, void *arg);
|
||||
static int _shutdown(json_rpc_request_t *req, cJSON **resp, void *arg);
|
||||
|
||||
static void _exit_notf(json_rpc_request_t *req, void *arg);
|
||||
|
||||
int
|
||||
pawn_lsp_init(pawn_lsp_t *pawn,
|
||||
json_rpc_t *jrpc,
|
||||
const char *pawncc,
|
||||
char **pawn_args,
|
||||
int pawn_argc)
|
||||
{
|
||||
pawn->jrpc = jrpc;
|
||||
pawn->pawncc = strdup(pawncc);
|
||||
|
||||
/* execv(pawncc <filename> -d0 -v=2 -o/tmp/... [...args] <NULL>)*/
|
||||
pawn->pawn_args = calloc(pawn_argc + 5, sizeof(char *));
|
||||
/* pawn->pawn_args[0] = <filename>, set when compiling */
|
||||
pawn->pawn_args[1] = strdup("-d0");
|
||||
pawn->pawn_args[2] = strdup("-v=2");
|
||||
/* pawn->pawn_args[3] = -o/tmp/..., set when compiling */
|
||||
for (int i = 0; i < pawn_argc; i++) {
|
||||
pawn->pawn_args[i + 4] = strdup(pawn_args[i]);
|
||||
}
|
||||
|
||||
json_rpc_set_request_handler(jrpc, _request_handler, &pawn);
|
||||
json_rpc_set_notify_handler(jrpc, _notify_handler, &pawn);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
pawn_lsp_deinit(pawn_lsp_t *pawn)
|
||||
{
|
||||
free(pawn->pawncc);
|
||||
for (int i = 0; pawn->pawn_args[i] != NULL; i++) {
|
||||
free(pawn->pawn_args[i]);
|
||||
}
|
||||
free(pawn->pawn_args);
|
||||
}
|
||||
|
||||
static int
|
||||
_request_handler(json_rpc_request_t *req, cJSON **resp, void *arg)
|
||||
{
|
||||
char *method = req->method->valuestring;
|
||||
|
||||
log_info("Handle request \"%s\"\r\n", method);
|
||||
|
||||
if (!strcmp(method, "initialize")) {
|
||||
return _initialize(req, resp, arg);
|
||||
} else if (!strcmp(method, "shutdown")) {
|
||||
return _shutdown(req, resp, arg);
|
||||
} else {
|
||||
log_warn("Unrecognized request method\r\n");
|
||||
(*resp) = cJSON_CreateNull();
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_notify_handler(json_rpc_request_t *req, void *arg)
|
||||
{
|
||||
char *method = req->method->valuestring;
|
||||
|
||||
log_info("Handle notification \"%s\"\r\n", method);
|
||||
|
||||
if (!strcmp(method, "exit")) {
|
||||
_exit_notf(req, arg);
|
||||
} else {
|
||||
log_warn("Unrecognized notification method\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
_initialize(json_rpc_request_t *req, cJSON **resp, void *arg)
|
||||
{
|
||||
cJSON *val;
|
||||
|
||||
(*resp) = cJSON_CreateObject();
|
||||
|
||||
val = cJSON_AddObjectToObject((*resp), "capabilities");
|
||||
if (val == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (cJSON_AddStringToObject(val, "positionEncoding", "utf-8") == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
val = cJSON_AddObjectToObject((*resp), "serverInfo");
|
||||
if (val == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if ((cJSON_AddStringToObject(val, "name", PAWN_LSP_NAME) == NULL) ||
|
||||
(cJSON_AddStringToObject(val, "version", PAWN_LSP_VESRION) == NULL)) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
_shutdown(json_rpc_request_t *req, cJSON **resp, void *arg)
|
||||
{
|
||||
(*resp) = cJSON_CreateNull();
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
_exit_notf(json_rpc_request_t *req, void *arg)
|
||||
{
|
||||
pawn_lsp_t *pawn = arg;
|
||||
json_rpc_cancel(pawn->jrpc);
|
||||
}
|
||||
|
||||
27
src/pawn_lsp.h
Normal file
27
src/pawn_lsp.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef _PAWN_LSP_H_
|
||||
#define _PAWN_LSP_H_
|
||||
|
||||
#include "json_rpc.h"
|
||||
|
||||
typedef struct pawn_lsp_struct {
|
||||
/** Active JRPC instance */
|
||||
json_rpc_t *jrpc;
|
||||
/** Path to the pawncc compiler */
|
||||
char *pawncc;
|
||||
/** User provided args for pawncc */
|
||||
char **pawn_args;
|
||||
} pawn_lsp_t;
|
||||
|
||||
/**
|
||||
* @brief Register JSON RPC methods for the Pawn language server
|
||||
*/
|
||||
int pawn_lsp_init(pawn_lsp_t *pawn,
|
||||
json_rpc_t *jrpc,
|
||||
const char *pawncc,
|
||||
char **pawn_argv,
|
||||
int pawn_argc);
|
||||
|
||||
void pawn_lsp_deinit(pawn_lsp_t *pawn);
|
||||
|
||||
#endif /* _PAWN_LSP_H_ */
|
||||
|
||||
Loading…
Reference in a new issue