jak-project/lsp/main.cpp
Tyler Wilding 53277a65ad
LSP: A bunch of new OpenGOAL language features (#3437)
- Integrate the AST into the LSP, this makes parsing and tokenizing the
files much easier
- Consolidate most of the symbol info tracking in `goalc` to a single
map. Fixed some issues where the old map would never evict symbols when
re-compiling files. There is still some more to cleanup, but this now
can be used as an incrementally updated source-of-truth for the LSP
- re-compile files when they are saved. Ideally this would be done
everytime they are changed but that:
  - may be too aggressive
- goalc doesn't compile incrementally yet so it likely would be a worse
UX

Features added, see
https://github.com/open-goal/opengoal-vscode/issues/256
- Hover

![image](https://github.com/open-goal/jak-project/assets/13153231/58dadb5d-582c-4c1f-9ffe-eaa4c85a0255)

![image](https://github.com/open-goal/jak-project/assets/13153231/b383adde-57fc-462c-a256-b2de5c30ca9a)
- LSP Status fixed
- Type Hierarchy

![image](https://github.com/open-goal/jak-project/assets/13153231/8e681377-1d4e-4336-ad70-1695a4607340)
- Document Color

![image](https://github.com/open-goal/jak-project/assets/13153231/4e48ccd8-0ed1-4459-a133-5277561e4201)
- Document Symbols
![Screenshot 2024-03-27
004105](https://github.com/open-goal/jak-project/assets/13153231/8e655034-43c4-4261-b6e0-85de00cbfc7f)
- Completions
![Screenshot 2024-03-30
004504](https://github.com/open-goal/jak-project/assets/13153231/d123a187-af90-466b-9eb7-561b2ee97cd1)

---------

Co-authored-by: Hat Kid <6624576+Hat-Kid@users.noreply.github.com>
2024-03-30 19:49:07 -04:00

159 lines
4.7 KiB
C++

// clang-format off
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <optional>
#include <vector>
#include <regex>
#include "common/log/log.h"
#include "common/util/unicode_util.h"
#include "common/util/term_util.h"
#include "lsp/handlers/lsp_router.h"
#include "lsp/state/workspace.h"
#include "lsp/transport/stdio.h"
#include "lsp/state/app.h"
#include "third-party/CLI11.hpp"
// clang-format on
// NOTE - if we ever add HTTP support to the LSP
/*
What needs to be understood is that for connection timing issues the server is actually a client
and the client is the server in terms of opening the ports.
When you specify a socket transport the client is listening on that port for a connection. The
socket port number is passed as --socket=${port} to the server process started.
*/
void setup_logging(bool verbose, std::string log_file, bool disable_ansi_colors) {
if (!log_file.empty()) {
lg::set_file(fs::path(log_file).filename().string(), false, true,
fs::path(log_file).parent_path().string());
}
if (verbose) {
lg::set_file_level(lg::level::debug);
lg::set_flush_level(lg::level::debug);
} else {
lg::set_file_level(lg::level::info);
lg::set_flush_level(lg::level::info);
}
if (disable_ansi_colors) {
lg::disable_ansi_colors();
}
// We use stdout to communicate with the client, so don't use it at all!
lg::set_stdout_level(lg::level::off);
lg::initialize();
}
std::string temp_url_encode(const std::string& value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || c == '/') {
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char)c);
escaped << std::nouppercase;
}
return escaped.str();
}
int main(int argc, char** argv) {
ArgumentGuard u8_guard(argc, argv);
CLI::App app{"OpenGOAL Language Server"};
bool use_stdin = true;
bool verbose = false;
std::string logfile;
app.add_flag("--stdio", use_stdin,
"Don't launch an HTTP server and instead accept input on stdin");
app.add_flag("-v,--verbose", verbose, "Enable verbose logging");
app.add_option("-l,--log", logfile, "Log file path");
define_common_cli_arguments(app);
app.validate_positionals();
CLI11_PARSE(app, argc, argv);
AppState appstate;
LSPRouter lsp_router;
appstate.verbose = verbose;
try {
setup_logging(appstate.verbose, logfile, _cli_flag_disable_ansi);
} catch (const std::exception& e) {
lg::error("Failed to setup logging: {}", e.what());
return 1;
}
lsp_router.init_routes();
lg::info("OpenGOAL LSP Initialized, ready for requests");
#ifdef _WIN32
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stdin), _O_BINARY);
#endif
// TODO - make the server check for the process id of the extension host and exit itself if that
// process goes away (the process id comes on the command line as an argument and in the
// initialize request). This is what we do in all our servers since the extension host could die
// unexpected as well.
try {
char c;
MessageBuffer message_buffer;
while (std::cin.get(c)) {
message_buffer.handle_char(c);
if (message_buffer.message_completed()) {
json body = message_buffer.body();
// If the request doesn't have a 'method', then it's not a request
// skip it, but log it. We don't depend on any requests from the client yet
// currently they are mostly just notifications
if (!body.contains("method")) {
lg::warn("Response received from client - {}", body.dump());
message_buffer.clear();
continue;
}
auto method_name = body["method"].get<std::string>();
lg::info(">>> Received message of method '{}'", method_name);
auto responses = lsp_router.route_message(message_buffer, appstate);
if (responses) {
for (const auto& response : responses.value()) {
std::cout << response.c_str() << std::flush;
if (appstate.verbose) {
lg::debug("<<< Sending message: {}", response);
} else {
lg::info("<<< Sending message of method '{}'", method_name);
}
}
}
message_buffer.clear();
}
}
} catch (std::exception& e) {
lg::error("Unexpected LSP Exception occured - {}", e.what());
}
return 0;
}