Skip to content

Commit

Permalink
feat: Code completion for machine instructions and mnemonics utilizes…
Browse files Browse the repository at this point in the history
… snippets
  • Loading branch information
slavek-kucera authored Aug 3, 2022
1 parent e94f03d commit b17bc3a
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 93 deletions.
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Command for downloading copybook datasets from the mainframe
- Support for multiple preprocessors
- Preprocessor for expanding Endevor includes
- Code completion for machine instructions and mnemonics utilizes snippets

### Fixed
- Improve performance of file system event processing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ suite('Integration Test Suite', () => {
const text = editor.document.getText();
const acceptedLine = text.split('\n')[7];

assert.ok(acceptedLine.includes('L R,D12U(X,B)'), 'Wrong suggestion result' + acceptedLine);
assert.ok(acceptedLine.includes('L R,D12U(X,B)'), 'Wrong suggestion result' + acceptedLine);
}).timeout(10000).slow(4000);

// test completion for variable symbols
Expand Down
7 changes: 5 additions & 2 deletions language_server/src/lsp/feature_language_features.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,14 @@ void feature_language_features::completion(const json& id, const json& params)
for (size_t i = 0; i < completion_list.size(); ++i)
{
const auto& item = completion_list.item(i);
completion_item_array.push_back(json { { "label", item.label() },
completion_item_array.push_back(json {
{ "label", item.label() },
{ "kind", completion_item_kind_mapping.at(item.kind()) },
{ "detail", item.detail() },
{ "documentation", get_markup_content(item.documentation()) },
{ "insertText", item.insert_text() } });
{ "insertText", item.insert_text() },
{ "insertTextFormat", 1 + (int)item.is_snippet() },
});
}
to_ret = json { { "isIncomplete", false }, { "items", completion_item_array } };

Expand Down
1 change: 1 addition & 0 deletions parser_library/include/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ struct PARSER_LIBRARY_EXPORT completion_item
std::string_view detail() const;
std::string_view documentation() const;
std::string_view insert_text() const;
bool is_snippet() const;

private:
const lsp::completion_item_s& item_;
Expand Down
158 changes: 73 additions & 85 deletions parser_library/src/lsp/completion_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,74 +24,66 @@ completion_item_s::completion_item_s(std::string label,
std::string detail,
std::string insert_text,
std::string documentation,
completion_item_kind kind)
completion_item_kind kind,
bool snippet)
: label(std::move(label))
, detail(std::move(detail))
, insert_text(std::move(insert_text))
, documentation(std::move(documentation))
, kind(kind)
, snippet(snippet)
{}

namespace {
void process_machine_instruction(const context::machine_instruction& machine_instr,
std::set<completion_item_s, completion_item_s::label_comparer>& items)
{
std::stringstream doc_ss(" ");
std::stringstream doc_ss("");
std::stringstream detail_ss(""); // operands used for hover - e.g. V,D12U(X,B)[,M]
std::stringstream autocomplete(""); // operands used for autocomplete - e.g. V,D12U(X,B) [,M]

autocomplete << machine_instr.name() << " ";

size_t snippet_id = 1;
bool first_optional = true;
for (size_t i = 0; i < machine_instr.operands().size(); i++)
{
const auto& op = machine_instr.operands()[i];
if (machine_instr.optional_operand_count() == 1 && machine_instr.operands().size() - i == 1)
const bool is_optional = machine_instr.operands().size() - i <= machine_instr.optional_operand_count();
if (is_optional && first_optional)
{
autocomplete << " [";
first_optional = false;
autocomplete << "${" << snippet_id++ << ": [";
detail_ss << "[";
if (i != 0)
{
autocomplete << ",";
detail_ss << ",";
}
detail_ss << op.to_string() << "]";
autocomplete << op.to_string() << "]";
}
else if (machine_instr.optional_operand_count() == 2 && machine_instr.operands().size() - i == 2)
if (i != 0)
{
autocomplete << " [";
detail_ss << "[";
if (i != 0)
{
autocomplete << ",";
detail_ss << ",";
}
detail_ss << op.to_string() << "]";
autocomplete << op.to_string() << "[,";
autocomplete << ",";
detail_ss << ",";
}
else if (machine_instr.optional_operand_count() == 2 && machine_instr.operands().size() - i == 1)
if (!is_optional)
{
detail_ss << op.to_string() << "]]";
autocomplete << op.to_string() << "]]";
detail_ss << op.to_string();
autocomplete << "${" << snippet_id++ << ":" << op.to_string() << "}";
}
else if (machine_instr.operands().size() - i > 1)
{
detail_ss << op.to_string() << "[";
autocomplete << op.to_string() << "[";
}
else
{
if (i != 0)
{
autocomplete << ",";
detail_ss << ",";
}
detail_ss << op.to_string();
autocomplete << op.to_string();
detail_ss << op.to_string() << std::string(machine_instr.optional_operand_count(), ']');
autocomplete << op.to_string() << std::string(machine_instr.optional_operand_count(), ']') << "}";
}
}
doc_ss << "Machine instruction " << std::endl
doc_ss << "Machine instruction "
<< "\n\n"
<< "Instruction format: " << context::instruction::mach_format_to_string(machine_instr.format());
items.emplace(std::string(machine_instr.name()),
"Operands: " + detail_ss.str(),
autocomplete.str(),
std::string(machine_instr.name()) + " ${" + std::to_string(snippet_id++) + ":}" + autocomplete.str(),
doc_ss.str(),
completion_item_kind::mach_instr);
completion_item_kind::mach_instr,
true);
}

void process_assembler_instruction(const context::assembler_instruction& asm_instr,
Expand All @@ -112,18 +104,24 @@ void process_assembler_instruction(const context::assembler_instruction& asm_ins
void process_mnemonic_code(
const context::mnemonic_code& mnemonic_instr, std::set<completion_item_s, completion_item_s::label_comparer>& items)
{
std::stringstream doc_ss(" ");
std::stringstream doc_ss("");
std::stringstream detail_ss("");
std::stringstream subs_ops_mnems(" ");
std::stringstream subs_ops_nomnems(" ");
std::stringstream subs_ops_mnems("");
std::stringstream subs_ops_nomnems("");
std::stringstream subs_ops_nomnems_no_snippets("");

// get mnemonic operands
size_t iter_over_mnem = 0;
size_t snippet_id = 1;
bool first_optional = true;

const auto& mach_operands = mnemonic_instr.instruction()->operands();
auto no_optional = mnemonic_instr.instruction()->optional_operand_count();
const auto optional_count = mnemonic_instr.instruction()->optional_operand_count();
bool first = true;
std::vector<std::string> mnemonic_with_operand_ommited = { "VNOT", "NOTR", "NOTGR" };
constexpr const auto is_mnemonic_with_operand_ommited = [](std::string_view name) {
static constexpr const std::string_view list[] = { "VNOT", "NOTR", "NOTGR" };
return std::find(std::begin(list), std::end(list), name) != std::end(list);
};


auto replaces = mnemonic_instr.replaced_operands();
Expand All @@ -136,8 +134,8 @@ void process_mnemonic_code(
// can still replace mnemonics
if (position == i)
{
// mnemonics can be substituted when no_optional is 1, but not 2 -> 2 not implemented
if (no_optional == 1 && mach_operands.size() - i == 1)
// mnemonics can be substituted when optional_count is 1, but not 2 -> 2 not implemented
if (optional_count == 1 && mach_operands.size() - i == 1)
{
subs_ops_mnems << "[";
if (i != 0)
Expand All @@ -148,10 +146,7 @@ void process_mnemonic_code(
// replace current for mnemonic
if (i != 0)
subs_ops_mnems << ",";
if (std::find(mnemonic_with_operand_ommited.begin(),
mnemonic_with_operand_ommited.end(),
mnemonic_instr.name())
!= mnemonic_with_operand_ommited.end())
if (is_mnemonic_with_operand_ommited(mnemonic_instr.name()))
{
subs_ops_mnems << mach_operands[i - 1].to_string();
}
Expand All @@ -161,59 +156,52 @@ void process_mnemonic_code(
continue;
}
}
// do not replace by a mnemonic
std::string curr_op_with_mnem = "";
std::string curr_op_without_mnem = "";
if (no_optional == 0)
const bool is_optional = mach_operands.size() - i <= optional_count;
if (is_optional && first_optional)
{
if (i != 0)
curr_op_with_mnem += ",";
if (!first)
curr_op_without_mnem += ",";
curr_op_with_mnem += mach_operands[i].to_string();
curr_op_without_mnem += mach_operands[i].to_string();
first_optional = false;
subs_ops_mnems << " [";
subs_ops_nomnems << "${" << snippet_id++ << ": [";
subs_ops_nomnems_no_snippets << " [";
}
else if (no_optional == 1 && mach_operands.size() - i == 1)
if (i != 0)
subs_ops_mnems << ",";
if (!first)
subs_ops_nomnems << ",";
if (!first)
subs_ops_nomnems_no_snippets << ",";
if (!is_optional)
{
curr_op_with_mnem += "[";
curr_op_without_mnem += "[";
if (i != 0)
curr_op_with_mnem += ",";
if (!first)
curr_op_without_mnem += ",";
curr_op_with_mnem += mach_operands[i].to_string() + "]";
curr_op_without_mnem += mach_operands[i].to_string() + "]";
subs_ops_mnems << mach_operands[i].to_string();
subs_ops_nomnems << "${" << snippet_id++ << ":" << mach_operands[i].to_string() << "}";
subs_ops_nomnems_no_snippets << mach_operands[i].to_string();
}
else if (no_optional == 2 && mach_operands.size() - i == 1)
else if (mach_operands.size() - i > 1)
{
curr_op_with_mnem += mach_operands[i].to_string() + "]]";
curr_op_without_mnem += mach_operands[i].to_string() + "]]";
subs_ops_mnems << mach_operands[i].to_string() + "[";
subs_ops_nomnems << mach_operands[i].to_string() << "[";
subs_ops_nomnems_no_snippets << mach_operands[i].to_string() << "[";
}
else if (no_optional == 2 && mach_operands.size() - i == 2)
else
{
curr_op_with_mnem += "[";
curr_op_without_mnem += "[";
if (i != 0)
curr_op_with_mnem += ",";
if (!first)
curr_op_without_mnem += ",";
curr_op_with_mnem += mach_operands[i].to_string() + "[,";
curr_op_without_mnem += mach_operands[i].to_string() + "[,";
subs_ops_mnems << mach_operands[i].to_string() + std::string(optional_count, ']');
subs_ops_nomnems << mach_operands[i].to_string() << std::string(optional_count, ']') << "}";
subs_ops_nomnems_no_snippets << mach_operands[i].to_string() << std::string(optional_count, ']');
}
subs_ops_mnems << curr_op_with_mnem;
subs_ops_nomnems << curr_op_without_mnem;
first = false;
}
detail_ss << "Operands: " + subs_ops_nomnems.str();
doc_ss << "Mnemonic code for " << mnemonic_instr.instruction()->name() << " instruction" << std::endl
<< "Substituted operands: " << subs_ops_mnems.str() << std::endl
detail_ss << "Operands: " + subs_ops_nomnems_no_snippets.str();
doc_ss << "Mnemonic code for " << mnemonic_instr.instruction()->name() << " instruction"
<< "\n\n"
<< "Substituted operands: " << subs_ops_mnems.str() << "\n\n"
<< "Instruction format: "
<< context::instruction::mach_format_to_string(mnemonic_instr.instruction()->format());
items.emplace(std::string(mnemonic_instr.name()),
detail_ss.str(),
std::string(mnemonic_instr.name()) + " " + subs_ops_nomnems.str(),
std::string(mnemonic_instr.name()) + " ${" + std::to_string(snippet_id++) + ":}" + subs_ops_nomnems.str(),
doc_ss.str(),
completion_item_kind::mach_instr);
completion_item_kind::mach_instr,
true);
}

void process_ca_instruction(
Expand Down
4 changes: 3 additions & 1 deletion parser_library/src/lsp/completion_item.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ struct completion_item_s
std::string detail,
std::string insert_text,
std::string documentation,
completion_item_kind kind = completion_item_kind::mach_instr);
completion_item_kind kind = completion_item_kind::mach_instr,
bool snippet = false);

// several features of completion item from LSP
std::string label;
std::string detail;
std::string insert_text;
std::string documentation;
completion_item_kind kind;
bool snippet = false;

struct label_comparer
{
Expand Down
17 changes: 14 additions & 3 deletions parser_library/src/lsp/lsp_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,16 @@ std::string lsp_context::get_macro_documentation(const macro_info& m) const
return result;
}

completion_list_s lsp_context::complete_instr(const file_info&, position) const
completion_list_s lsp_context::complete_instr(const file_info& fi, position pos) const
{
completion_list_s result;

auto completion_start = fi.data.get_line(pos.line).substr(0, pos.column).rfind(' ');
if (completion_start == std::string_view::npos)
completion_start = pos.column;
else
completion_start += 2; // after and turn to column

// Store only instructions from the currently active instruction set
for (const auto& instr : completion_item_s::m_instruction_completion_items)
{
Expand All @@ -838,7 +844,12 @@ completion_list_s lsp_context::complete_instr(const file_info&, position) const
auto it = m_hlasm_ctx->instruction_map().find(id);
if (it != m_hlasm_ctx->instruction_map().end())
{
result.emplace_back(instr);
auto& i = result.emplace_back(instr);
if (auto space = i.insert_text.find(' '); space != std::string::npos)
{
if (completion_start + space < 15)
i.insert_text.insert(i.insert_text.begin() + space, 15 - (completion_start + space), ' ');
}
}
}

Expand Down Expand Up @@ -991,7 +1002,7 @@ hover_result lsp_context::find_hover(const symbol_occurence& occ, macro_info_ptr
auto it = completion_item_s::m_instruction_completion_items.find(*occ.name);
if (it == completion_item_s::m_instruction_completion_items.end())
return "";
return it->detail + " \n" + it->documentation;
return it->detail + "\n\n" + it->documentation;
}
}
case lsp::occurence_kind::COPY_OP:
Expand Down
1 change: 1 addition & 0 deletions parser_library/src/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ completion_item_kind completion_item::kind() const { return item_.kind; }
std::string_view completion_item::detail() const { return item_.detail; }
std::string_view completion_item::documentation() const { return item_.documentation; }
std::string_view completion_item::insert_text() const { return item_.insert_text; }
bool completion_item::is_snippet() const { return item_.snippet; }

completion_item sequence_item_get(const sequence<completion_item, const lsp::completion_item_s*>* self, size_t index)
{
Expand Down
2 changes: 1 addition & 1 deletion parser_library/test/workspace/processor_file_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ TEST(processor_file, parse_macro)

EXPECT_EQ(open_fp.definition(opencode_loc, { 1, 2 }), location({ 1, 1 }, macro_loc));

const std::string sam31_hover_message = "Operands: \nMachine instruction \nInstruction format: E";
const std::string sam31_hover_message = "Operands: \n\nMachine instruction \n\nInstruction format: E";
EXPECT_EQ(open_fp.hover(opencode_loc, { 0, 2 }), sam31_hover_message);
EXPECT_EQ(open_fp.hover(macro_loc, { 2, 2 }), sam31_hover_message);

Expand Down

0 comments on commit b17bc3a

Please sign in to comment.