Skip to content

Commit

Permalink
fix: Refactor the way of collecting LSP information (#110)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Smelko <adamsmelko1@gmail.com>
  • Loading branch information
michalbali256 and asmelko authored Mar 29, 2021
1 parent 044acad commit d767b6d
Show file tree
Hide file tree
Showing 204 changed files with 6,166 additions and 3,441 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,11 @@ if(UNIX)

try_run(TEST_LINK_RUN_RESULT TEST_LINK_COMPILE_RESULT ${CMAKE_BINARY_DIR}/try_filesystem_link_test ${CMAKE_SOURCE_DIR}/cmake/try_filesystem_link.cpp
CMAKE_FLAGS ${CMAKE_FLAGS_PASS} ${TEST_LINK_LIBS}
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT_RESULT
)

if(NOT (TEST_LINK_RUN_RESULT EQUAL "0") OR NOT ${TEST_LINK_COMPILE_RESULT})
message(FATAL_ERROR "Filesystem link error: ${CMAKE_SOURCE_DIR}/cmake/try_filesystem_link.cpp could not be compiled.")
message(FATAL_ERROR "Filesystem link error: ${CMAKE_SOURCE_DIR}/cmake/try_filesystem_link.cpp could not be compiled. Message: \n ${COMPILE_OUTPUT_RESULT}")
endif()
endif()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ suite('Integration Test Suite', () => {
.then((result: vscode.Hover[]) => {
if (result.length == 1
&& result[0].contents.length == 1
&& (result[0].contents[0] as vscode.MarkdownString).value == 'number')
&& (result[0].contents[0] as vscode.MarkdownString).value == 'SETA variable')
done();
else
done('Wrong variable symbol hover contents');
Expand Down
4 changes: 4 additions & 0 deletions clients/vscode-hlasmplugin/syntaxes/hlasm.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
{
"name": "comment.hlasm",
"match": "\\*.*"
},
{
"name": "comment.hlasm",
"match": "\\.\\*.*"
}
]
},
Expand Down
14 changes: 7 additions & 7 deletions language_server/src/feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,20 @@ std::string feature::uri_to_path(const std::string& uri)
return p.lexically_normal().string();
}

std::string feature::path_to_uri(std::string path)
std::string feature::path_to_uri(std::string_view path)
{
if (path.substr(0, untitled.size()) == untitled)
return path;
return std::string(path);

std::replace(path.begin(), path.end(), '\\', '/');
// network::detail::encode_path(uri) ignores @, which is incompatible with VS Code
std::string uri;
auto out = std::back_inserter(uri);
auto it = path.cbegin();
while (it != path.cend())

for (char c : path)
{
network::detail::encode_char(*it, out, "/.%;=");
++it;
if (c == '\\')
c = '/';
network::detail::encode_char(c, out, "/.%;=");
}

#ifdef _WIN32
Expand Down
2 changes: 1 addition & 1 deletion language_server/src/feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class feature
// Converts URI (RFC3986) to common filesystem path.
static std::string uri_to_path(const std::string& uri);
// Converts from filesystem path to URI
static std::string path_to_uri(std::string path);
static std::string path_to_uri(std::string_view path);

// Converts LSP json representation of range into parse_library::range.
static parser_library::range parse_range(const json& range_json);
Expand Down
84 changes: 67 additions & 17 deletions language_server/src/lsp/feature_language_features.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ void feature_language_features::definition(const json& id, const json& params)
auto pos =
parser_library::position(params["position"]["line"].get<int>(), params["position"]["character"].get<int>());


auto definition_position_uri = ws_mngr_.definition(uri_to_path(document_uri).c_str(), pos);
document_uri =
(definition_position_uri.uri()[0] == '\0') ? document_uri : path_to_uri(definition_position_uri.uri());
(definition_position_uri.file()[0] == '\0') ? document_uri : path_to_uri(definition_position_uri.file());
json to_ret { { "uri", document_uri },
{ "range", range_to_json({ definition_position_uri.pos(), definition_position_uri.pos() }) } };
response_->respond(id, "", to_ret);
Expand All @@ -101,9 +102,9 @@ void feature_language_features::references(const json& id, const json& params)
auto references = ws_mngr_.references(uri_to_path(document_uri).c_str(), pos);
for (size_t i = 0; i < references.size(); ++i)
{
auto ref = references.get_position_uri(i);
auto ref = references.item(i);
to_ret.push_back(
json { { "uri", path_to_uri(ref.uri()) }, { "range", range_to_json({ ref.pos(), ref.pos() }) } });
json { { "uri", path_to_uri(ref.file()) }, { "range", range_to_json({ ref.pos(), ref.pos() }) } });
}
response_->respond(id, "", to_ret);
}
Expand All @@ -113,38 +114,87 @@ void feature_language_features::hover(const json& id, const json& params)
auto pos =
parser_library::position(params["position"]["line"].get<int>(), params["position"]["character"].get<int>());

json hover_arr = json::array();

auto hover_list = ws_mngr_.hover(uri_to_path(document_uri).c_str(), pos);
for (size_t i = 0; i < hover_list.size; i++)
{
hover_arr.push_back(hover_list.arr[i]);
}
response_->respond(id, "", json { { "contents", hover_arr } });

response_->respond(id, "", json { { "contents", hover_list.empty() ? json() : get_markup_content(hover_list) } });
}

// Completion item kinds from the LSP specification
enum class lsp_completion_item_kind
{
text = 1,
method = 2,
function = 3,
constructor = 4,
field = 5,
variable = 6,
class_v = 7,
interface = 8,
module_v = 9,
property = 10,
unit = 11,
value = 12,
enum_v = 13,
keyword = 14,
snippet = 15,
color = 16,
file = 17,
reference = 18,
folder = 19,
enum_member = 20,
constant = 21,
struct_v = 22,
event = 23,
operator_v = 24,
type_parameter = 25
};


const std::unordered_map<parser_library::completion_item_kind, lsp_completion_item_kind> completion_item_kind_mapping {
{ parser_library::completion_item_kind::mach_instr, lsp_completion_item_kind::function },
{ parser_library::completion_item_kind::asm_instr, lsp_completion_item_kind::function },
{ parser_library::completion_item_kind::ca_instr, lsp_completion_item_kind::function },
{ parser_library::completion_item_kind::macro, lsp_completion_item_kind::file },
{ parser_library::completion_item_kind::var_sym, lsp_completion_item_kind::variable },
{ parser_library::completion_item_kind::seq_sym, lsp_completion_item_kind::reference }
};


json feature_language_features::get_markup_content(std::string_view content)
{
return json { { "kind", "markdown" }, { "value", content } };
}

void feature_language_features::completion(const json& id, const json& params)
{
auto document_uri = params["textDocument"]["uri"].get<std::string>();
auto pos =
parser_library::position(params["position"]["line"].get<int>(), params["position"]["character"].get<int>());

int trigger_kind_int = params["context"]["triggerKind"].get<int>();
parser_library::completion_trigger_kind trigger_kind = (trigger_kind_int >= 1 && trigger_kind_int <= 3)
? (parser_library::completion_trigger_kind)trigger_kind_int
: parser_library::completion_trigger_kind::invoked;

// no trigger character
char trigger_char = '\0';
int trigger_kind = params["context"]["triggerKind"].get<int>();
if (trigger_kind == 2)
if (trigger_kind == parser_library::completion_trigger_kind::trigger_character)
trigger_char = params["context"]["triggerCharacter"].get<std::string>()[0];

auto completion_list = ws_mngr_.completion(uri_to_path(document_uri).c_str(), pos, trigger_char, trigger_kind);
json to_ret = json::value_t::null;
json completion_item_array = json::array();
for (size_t i = 0; i < completion_list.count(); i++)
for (size_t i = 0; i < completion_list.size(); ++i)
{
auto item = completion_list.item(i);
const auto& item = completion_list.item(i);
completion_item_array.push_back(json { { "label", item.label() },
{ "kind", item.kind() },
{ "kind", completion_item_kind_mapping.at(item.kind()) },
{ "detail", item.detail() },
{ "documentation", item.documentation() },
{ "deprecated", item.deprecated() },
{ "documentation", get_markup_content(item.documentation()) },
{ "insertText", item.insert_text() } });
}
to_ret = json { { "isIncomplete", completion_list.is_incomplete() }, { "items", completion_item_array } };
to_ret = json { { "isIncomplete", false }, { "items", completion_item_array } };

response_->respond(id, "", to_ret);
}
Expand Down
2 changes: 2 additions & 0 deletions language_server/src/lsp/feature_language_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class feature_language_features : public feature
void hover(const json& id, const json& params);
void completion(const json& id, const json& params);
void semantic_tokens(const json& id, const json& params);

static json get_markup_content(std::string_view content);
};

} // namespace hlasm_plugin::language_server::lsp
Expand Down
4 changes: 2 additions & 2 deletions language_server/test/dap/dap_sessions_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class stream_json_sink : public json_sink
TEST(dap_sessions, simple_start_stop)
{
std::atomic<bool> term = false;
ws_mngr_mock mock_ws;
test::ws_mngr_mock mock_ws;
stream_json_sink session_out;
dap::session session(term, mock_ws, session_out);

Expand All @@ -61,7 +61,7 @@ TEST(dap_sessions, simple_start_stop)

TEST(dap_sessions, session_manager)
{
ws_mngr_mock mock_ws;
test::ws_mngr_mock mock_ws;
stream_json_sink session_out;
dap::session_manager sess_mgr(mock_ws, session_out);

Expand Down
33 changes: 16 additions & 17 deletions language_server/test/lsp/feature_language_features_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "../response_provider_mock.h"
#include "../ws_mngr_mock.h"
#include "lsp/feature_language_features.h"
#include "semantics/lsp_info_processor.h"

#ifdef _WIN32
constexpr const char* path = "c:\\test";
Expand All @@ -34,7 +33,7 @@ using namespace hlasm_plugin::language_server;
TEST(language_features, completion)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
NiceMock<response_provider_mock> response_mock;
lsp::feature_language_features f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand All @@ -46,17 +45,17 @@ TEST(language_features, completion)
json params1 =
R"({"textDocument":{"uri":"file:///home/test"},"position":{"line":0,"character":1},"context":{"triggerKind":1}})"_json;
#endif
std::vector<context::completion_item_s> item_list = { context::completion_item_s(
"LR", "machine", "LR", std::vector<std::string> { "machine doc" }) };
auto list_s = semantics::completion_list_s(false, item_list);
EXPECT_CALL(ws_mngr, completion(StrEq(path), position(0, 1), '\0', 1)).WillOnce(Return(completion_list(list_s)));

EXPECT_CALL(ws_mngr,
completion(
StrEq(path), parser_library::position(0, 1), '\0', parser_library::completion_trigger_kind::invoked));
notifs["textDocument/completion"]("", params1);
}

TEST(language_features, hover)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
NiceMock<response_provider_mock> response_mock;
lsp::feature_language_features f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand All @@ -67,16 +66,18 @@ TEST(language_features, hover)
json params1 = R"({"textDocument":{"uri":"file:///home/test"},"position":{"line":0,"character":1}})"_json;
#endif
std::string s("test");
std::vector<const char*> coutput = { s.c_str() };
const string_array ret({ coutput.data(), coutput.size() });
EXPECT_CALL(ws_mngr, hover(StrEq(path), position(0, 1))).WillOnce(Return(ret));
std::string_view ret(s);
EXPECT_CALL(ws_mngr, hover(StrEq(path), parser_library::position(0, 1))).WillOnce(Return(ret));
notifs["textDocument/hover"]("", params1);
}

TEST(language_features, definition)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;


parser_library::workspace_manager ws_mngr;

NiceMock<response_provider_mock> response_mock;
lsp::feature_language_features f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand All @@ -87,15 +88,14 @@ TEST(language_features, definition)
json params1 = R"({"textDocument":{"uri":"file:///home/test"},"position":{"line":0,"character":1}})"_json;
#endif

semantics::position_uri_s pos_s(path, position(0, 1));
EXPECT_CALL(ws_mngr, definition(StrEq(path), position(0, 1))).WillOnce(Return(position_uri(pos_s)));
EXPECT_CALL(response_mock, respond(json(""), "", ::testing::_));
notifs["textDocument/definition"]("", params1);
}

TEST(language_features, references)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
NiceMock<response_provider_mock> response_mock;
lsp::feature_language_features f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand All @@ -105,9 +105,8 @@ TEST(language_features, references)
#else
json params1 = R"({"textDocument":{"uri":"file:///home/test"},"position":{"line":0,"character":1}})"_json;
#endif
std::vector<semantics::position_uri_s> ret = { semantics::position_uri_s(path, position(0, 1)) };
EXPECT_CALL(ws_mngr, references(StrEq(path), position(0, 1)))
.WillOnce(Return(position_uris(ret.data(), ret.size())));

EXPECT_CALL(ws_mngr, references(StrEq(path), parser_library::position(0, 1)));
notifs["textDocument/references"]("", params1);
}

Expand Down
13 changes: 8 additions & 5 deletions language_server/test/lsp/feature_text_synchronization_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const std::string txt_file_uri = R"(file:///home/user/somefile)";
const std::string txt_file_path = R"(/home/user/somefile)";
#endif

using namespace hlasm_plugin;
using namespace hlasm_plugin::language_server;


TEST(text_synchronization, did_open_file)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
response_provider_mock response_mock;
lsp::feature_text_synchronization f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand Down Expand Up @@ -65,7 +67,7 @@ MATCHER_P2(PointerAndSizeEqArray, pointer, size, "")
TEST(text_synchronization, did_change_file)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
response_provider_mock response_mock;
lsp::feature_text_synchronization f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand All @@ -74,15 +76,16 @@ TEST(text_synchronization, did_change_file)
json params1 = json::parse(R"({"textDocument":{"uri":")" + txt_file_uri
+ R"(","version":7},"contentChanges":[{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":8}},"rangeLength":8,"text":"sad"}, {"range":{"start":{"line":1,"character":12},"end":{"line":1,"character":14}},"rangeLength":2,"text":""}]})");

document_change expected1[2] { { { { 0, 0 }, { 0, 8 } }, "sad", 3 }, { { { 1, 12 }, { 1, 14 } }, "", 0 } };
parser_library::document_change expected1[2] { { { { 0, 0 }, { 0, 8 } }, "sad", 3 },
{ { { 1, 12 }, { 1, 14 } }, "", 0 } };

EXPECT_CALL(ws_mngr, did_change_file(StrEq(txt_file_path), 7, _, 2))
.With(Args<2, 3>(PointerAndSizeEqArray(expected1, std::size(expected1))));
notifs["textDocument/didChange"]("", params1);



document_change expected2[1] { { "sad", 3 } };
parser_library::document_change expected2[1] { { "sad", 3 } };
json params2 = json::parse(
R"({"textDocument":{"uri":")" + txt_file_uri + R"(","version":7},"contentChanges":[{"text":"sad"}]})");
EXPECT_CALL(ws_mngr, did_change_file(StrEq(txt_file_path), 7, _, 1))
Expand All @@ -101,7 +104,7 @@ TEST(text_synchronization, did_change_file)
TEST(text_synchronization, did_close_file)
{
using namespace ::testing;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
response_provider_mock response_mock;
lsp::feature_text_synchronization f(ws_mngr, response_mock);
std::map<std::string, method> notifs;
Expand Down
6 changes: 3 additions & 3 deletions language_server/test/lsp/lsp_server_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ TEST(lsp_server, initialize)
// this is json params actually sent by vscode LSP client
json j =
R"({"jsonrpc":"2.0","id":47,"method":"initialize","params":{"processId":5236,"rootPath":null,"rootUri":null,"capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"]}},"definition":{"dynamicRegistration":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}},"codeAction":{"dynamicRegistration":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true},"documentLink":{"dynamicRegistration":true},"typeDefinition":{"dynamicRegistration":true},"implementation":{"dynamicRegistration":true},"colorProvider":{"dynamicRegistration":true}}},"trace":"off","workspaceFolders":null}})"_json;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
send_message_provider_mock smpm;
lsp::server s(ws_mngr);
s.set_send_message_provider(&smpm);
Expand Down Expand Up @@ -89,7 +89,7 @@ TEST(lsp_server, initialize)
TEST(lsp_server, not_implemented_method)
{
json j = R"({"jsonrpc":"2.0","id":47,"method":"unknown_method","params":"A parameter"})"_json;
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
send_message_provider_mock smpm;
lsp::server s(ws_mngr);
s.set_send_message_provider(&smpm);
Expand Down Expand Up @@ -200,7 +200,7 @@ TEST(lsp_server, request_error_no_message)

TEST(lsp_server_test, wrong_message_received)
{
ws_mngr_mock ws_mngr;
test::ws_mngr_mock ws_mngr;
send_message_provider_mock smpm;
lsp::server s(ws_mngr);
s.set_send_message_provider(&smpm);
Expand Down
Loading

0 comments on commit d767b6d

Please sign in to comment.