Skip to content

Commit

Permalink
Restructure stuff to use an AppState struct
Browse files Browse the repository at this point in the history
  • Loading branch information
svenstaro committed Nov 28, 2017
1 parent d70cbcb commit 6eed502
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 55 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Expand Up @@ -46,3 +46,6 @@ target_link_libraries(glslls
stdc++fs
fmt::fmt-header-only
)

install(TARGETS glslls
RUNTIME DESTINATION bin)
6 changes: 1 addition & 5 deletions Makefile
@@ -1,15 +1,11 @@
.PHONY: build
build:
mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make -j$(shell nproc)
mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. && make -j$(shell nproc)

.PHONY: run
run: build
build/glslls

.PHONY: install
install:
install -Dm755 build/glslls $(DESTDIR)/$(PREFIX)/bin/glslls

.PHONY: clean
clean:
rm -rf build
10 changes: 10 additions & 0 deletions README.md
@@ -1,10 +1,20 @@
# glsl-language-server
Language server implementation for GLSL

## Status
Currently this LSP implementation can be interfaced with using either HTTP or stdio.

## Compile

git submodule update --init
mkdir build
cd build
cmake ..
make

You can also use the `Makefile` in the project root which is provided for convenience.

## Install

cd build
make install
127 changes: 79 additions & 48 deletions src/main.cpp
Expand Up @@ -26,8 +26,12 @@
using json = nlohmann::json;
namespace fs = std::experimental::filesystem;

const char* s_http_port = "8000";
struct mg_serve_http_opts s_http_server_opts;
struct AppState {
Workspace workspace;
bool verbose;
bool use_logfile;
std::ofstream logfile_stream;
};

std::string make_response(const json& response)
{
Expand Down Expand Up @@ -60,7 +64,7 @@ EShLanguage find_language(const std::string& name)
}

json get_diagnostics(std::string uri, std::string content,
bool use_logfile, std::ostream& logfile_stream, bool verbose = false)
AppState& appstate)
{
glslang::InitializeProcess();
auto document = uri;
Expand All @@ -74,8 +78,8 @@ json get_diagnostics(std::string uri, std::string content,
std::string debug_log = shader.getInfoLog();
glslang::FinalizeProcess();

if (use_logfile && verbose) {
fmt::print(logfile_stream, "Diagnostics raw output: {}\n" , debug_log);
if (appstate.use_logfile && appstate.verbose) {
fmt::print(appstate.logfile_stream, "Diagnostics raw output: {}\n" , debug_log);
}

std::regex re("(.*): 0:(\\d*): (.*)");
Expand All @@ -96,8 +100,8 @@ json get_diagnostics(std::string uri, std::string content,
severity_no = 2;
}
if (severity_no == -1) {
if (use_logfile) {
fmt::print(logfile_stream, "Error: Unknown severity '{}'\n", severity);
if (appstate.use_logfile) {
fmt::print(appstate.logfile_stream, "Error: Unknown severity '{}'\n", severity);
}
}
std::string message = trim(matches[3], " ");
Expand Down Expand Up @@ -143,20 +147,19 @@ json get_diagnostics(std::string uri, std::string content,
diagnostics.push_back(diagnostic);
}
}
if (use_logfile && verbose) {
fmt::print(logfile_stream, "Sending diagnostics: {}\n" , diagnostics);
if (appstate.use_logfile && appstate.verbose) {
fmt::print(appstate.logfile_stream, "Sending diagnostics: {}\n" , diagnostics);
}
logfile_stream.flush();
appstate.logfile_stream.flush();
return diagnostics;
}

std::optional<std::string> handle_message(const MessageBuffer& message_buffer, Workspace& workspace,
bool use_logfile, std::ofstream& logfile_stream, bool verbose = false)
std::optional<std::string> handle_message(const MessageBuffer& message_buffer, AppState& appstate)
{
json body = message_buffer.body();

if (body["method"] == "initialize") {
workspace.set_initialized(true);
appstate.workspace.set_initialized(true);

json text_document_sync{
{ "openClose", true },
Expand Down Expand Up @@ -218,9 +221,9 @@ std::optional<std::string> handle_message(const MessageBuffer& message_buffer, W
} else if (body["method"] == "textDocument/didOpen") {
auto uri = body["params"]["textDocument"]["uri"];
auto text = body["params"]["textDocument"]["text"];
workspace.add_document(uri, text);
appstate.workspace.add_document(uri, text);

json diagnostics = get_diagnostics(uri, text, use_logfile, logfile_stream, verbose);
json diagnostics = get_diagnostics(uri, text, appstate);
if (diagnostics.empty()) {
diagnostics = json::array();
}
Expand All @@ -235,10 +238,10 @@ std::optional<std::string> handle_message(const MessageBuffer& message_buffer, W
} else if (body["method"] == "textDocument/didChange") {
auto uri = body["params"]["textDocument"]["uri"];
auto change = body["params"]["contentChanges"][0]["text"];
workspace.change_document(uri, change);
appstate.workspace.change_document(uri, change);

std::string document = workspace.documents()[uri];
json diagnostics = get_diagnostics(uri, document, use_logfile, logfile_stream, verbose);
std::string document = appstate.workspace.documents()[uri];
json diagnostics = get_diagnostics(uri, document, appstate);
if (diagnostics.empty()) {
diagnostics = json::array();
}
Expand All @@ -255,7 +258,7 @@ std::optional<std::string> handle_message(const MessageBuffer& message_buffer, W
// If the workspace has not yet been initialized but the client sends a
// message that doesn't have method "initialize" then we'll return an error
// as per LSP spec.
if (body["method"] != "initialize" && !workspace.is_initialized()) {
if (body["method"] != "initialize" && !appstate.workspace.is_initialized()) {
json error{
{ "code", -32002 },
{ "message", "Server not yet initialized." },
Expand Down Expand Up @@ -289,15 +292,44 @@ std::optional<std::string> handle_message(const MessageBuffer& message_buffer, W
return make_response(result_body);
}

void ev_handler(struct mg_connection* c, int ev, void* p)
{
void ev_handler(struct mg_connection* c, int ev, void* p) {
AppState& appstate = *static_cast<AppState*>(c->mgr->user_data);

if (ev == MG_EV_HTTP_REQUEST) {
struct http_message* hm = (struct http_message*)p;

std::string content = hm->message.p;
std::string response = make_response({});
mg_send_head(c, 200, response.length(), "Content-Type: text/plain");
mg_printf(c, "%.*s", static_cast<int>(response.length()), response.c_str());

MessageBuffer message_buffer;
message_buffer.handle_string(content);

if (message_buffer.message_completed()) {
json body = message_buffer.body();
if (appstate.use_logfile) {
fmt::print(appstate.logfile_stream, ">>> Received message of type '{}'\n", body["method"].get<std::string>());
if (appstate.verbose) {
fmt::print(appstate.logfile_stream, "Headers:\n");
for (auto elem : message_buffer.headers()) {
auto pretty_header = fmt::format("{}: {}\n", elem.first, elem.second);
appstate.logfile_stream << pretty_header;
}
fmt::print(appstate.logfile_stream, "Body: \n{}\n\n", body.dump(4));
fmt::print(appstate.logfile_stream, "Raw: \n{}\n\n", message_buffer.raw());
}
}

auto message = handle_message(message_buffer, appstate);
if (message.has_value()) {
std::string response = message.value();
mg_send_head(c, 200, response.length(), "Content-Type: text/plain");
mg_printf(c, "%.*s", static_cast<int>(response.length()), response.c_str());
if (appstate.use_logfile && appstate.verbose) {
fmt::print(appstate.logfile_stream, "<<< Sending message: \n{}\n\n", message.value());
}
}
appstate.logfile_stream.flush();
message_buffer.clear();
}
}
}

Expand All @@ -320,24 +352,24 @@ int main(int argc, char* argv[])
return app.exit(e);
}

bool use_logfile = !logfile.empty();
std::ofstream logfile_stream;
if (use_logfile) {
logfile_stream.open(logfile);
AppState appstate;
appstate.verbose = verbose;
appstate.use_logfile = !logfile.empty();
if (appstate.use_logfile) {
appstate.logfile_stream.open(logfile);
}

Workspace workspace;

// TODO: HTTP Stuff probably doesn't work right now.
if (!use_stdin) {
struct mg_mgr mgr;
struct mg_connection* nc;
struct mg_bind_opts bind_opts;
std::memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.user_data = &appstate;

mg_mgr_init(&mgr, NULL);
// printf("Starting web server on port %s\n", s_http_port);
nc = mg_bind(&mgr, s_http_port, ev_handler);
fmt::print("Starting web server on port {}\n", port);
nc = mg_bind_opt(&mgr, std::to_string(port).c_str(), ev_handler, bind_opts);
if (nc == NULL) {
// printf("Failed to create listener\n");
return 1;
}

Expand All @@ -356,37 +388,36 @@ int main(int argc, char* argv[])

if (message_buffer.message_completed()) {
json body = message_buffer.body();
if (use_logfile) {
fmt::print(logfile_stream, ">>> Received message of type '{}'\n", body["method"].get<std::string>());
if (verbose) {
fmt::print(logfile_stream, "Headers:\n");
if (appstate.use_logfile) {
fmt::print(appstate.logfile_stream, ">>> Received message of type '{}'\n", body["method"].get<std::string>());
if (appstate.verbose) {
fmt::print(appstate.logfile_stream, "Headers:\n");
for (auto elem : message_buffer.headers()) {
auto pretty_header = fmt::format("{}: {}\n", elem.first, elem.second);
logfile_stream << pretty_header;
appstate.logfile_stream << pretty_header;
}
fmt::print(logfile_stream, "Body: \n{}\n\n", body.dump(4));
fmt::print(logfile_stream, "Raw: \n{}\n\n", message_buffer.raw());
fmt::print(appstate.logfile_stream, "Body: \n{}\n\n", body.dump(4));
fmt::print(appstate.logfile_stream, "Raw: \n{}\n\n", message_buffer.raw());
}
}

auto message = handle_message(message_buffer, workspace,
use_logfile, logfile_stream, verbose);
auto message = handle_message(message_buffer, appstate);
if (message.has_value()) {
fmt::print("{}", message.value());
std::cout << std::flush;

if (use_logfile && verbose) {
fmt::print(logfile_stream, "<<< Sending message: \n{}\n\n", message.value());
if (appstate.use_logfile && appstate.verbose) {
fmt::print(appstate.logfile_stream, "<<< Sending message: \n{}\n\n", message.value());
}
}
logfile_stream.flush();
appstate.logfile_stream.flush();
message_buffer.clear();
}
}
}

if (use_logfile) {
logfile_stream.close();
if (appstate.use_logfile) {
appstate.logfile_stream.close();
}

return 0;
Expand Down
33 changes: 31 additions & 2 deletions src/messagebuffer.cpp
Expand Up @@ -12,8 +12,37 @@ void MessageBuffer::handle_char(char c)
// If so, add it to our known headers.
// We'll also reset our string then.
if (!std::get<0>(new_header).empty()) {
// auto rofl1 = std::get<0>(new_header);
// auto rofl2 = std::get<1>(new_header);
m_headers[std::get<0>(new_header)] = std::get<1>(new_header);
m_raw_message.clear();
}

// A sole \r\n is the separator between the header block and the body block
// but we don't need it.
if (m_raw_message == "\r\n") {
m_raw_message.clear();
m_is_header_done = true;
}

if (m_is_header_done) {
// Now that we know that we're in the body, we just have to count until
// we reach the length of the body as provided in the Content-Length
// header.
auto content_length = std::stoi(m_headers["Content-Length"]);
if (m_raw_message.length() == content_length) {
m_body = json::parse(m_raw_message);
}
}
}

void MessageBuffer::handle_string(std::string s)
{
m_raw_message += s;

auto new_header = try_parse_header(m_raw_message);
// Check whether we were actually able to parse a header.
// If so, add it to our known headers.
// We'll also reset our string then.
if (!std::get<0>(new_header).empty()) {
m_headers[std::get<0>(new_header)] = std::get<1>(new_header);
m_raw_message.clear();
}
Expand Down
1 change: 1 addition & 0 deletions src/messagebuffer.hpp
Expand Up @@ -13,6 +13,7 @@ class MessageBuffer {
MessageBuffer();
virtual ~MessageBuffer();
void handle_char(char c);
void handle_string(std::string s);
const std::map<std::string, std::string>& headers() const;
const json& body() const;
const std::string& raw() const;
Expand Down

0 comments on commit 6eed502

Please sign in to comment.