Skip to content

Commit

Permalink
feat: Hover and Autocomplete for gas directives
Browse files Browse the repository at this point in the history
Clean up generic stuff for enums

Add uppercase support for directives

Fix as decoration in .file directive xml entry
  • Loading branch information
WillLillis committed Jan 19, 2024
1 parent e5da86e commit 8e419d6
Show file tree
Hide file tree
Showing 7 changed files with 1,233 additions and 41 deletions.
852 changes: 852 additions & 0 deletions directives/gas_directives.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion registers/x86_64.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<InstructionSet name="x86_64">
<InstructionSet name="x86-64">
<Register name="rax" description="Accumulator" type="General Purpose Register" width="64 bits">
</Register>
<Register name="eax" description="Accumulator" type="General Purpose Register" width="32 bits">
Expand Down
45 changes: 26 additions & 19 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn main() -> anyhow::Result<()> {
completion_item: Some(CompletionOptionsCompletionItem {
label_details_support: Some(true),
}),
trigger_characters: Some(vec![String::from("%")]),
trigger_characters: Some(vec![String::from("%"), String::from(".")]),
..Default::default()
});

Expand Down Expand Up @@ -82,10 +82,6 @@ pub fn main() -> anyhow::Result<()> {
let xml_conts_x86 = include_str!("../../opcodes/x86.xml");
populate_instructions(xml_conts_x86)?
.into_iter()
.map(|mut instruction| {
instruction.arch = Some(Arch::X86);
instruction
})
.map(|instruction| {
// filter out assemblers by user config
instr_filter_targets(&instruction, &target_config)
Expand All @@ -101,10 +97,6 @@ pub fn main() -> anyhow::Result<()> {
let xml_conts_x86_64 = include_str!("../../opcodes/x86_64.xml");
populate_instructions(xml_conts_x86_64)?
.into_iter()
.map(|mut instruction| {
instruction.arch = Some(Arch::X86_64);
instruction
})
.map(|instruction| {
// filter out assemblers by user config
instr_filter_targets(&instruction, &target_config)
Expand All @@ -131,10 +123,6 @@ pub fn main() -> anyhow::Result<()> {
let xml_conts_regs_x86 = include_str!("../../registers/x86.xml");
populate_registers(xml_conts_regs_x86)?
.into_iter()
.map(|mut reg| {
reg.arch = Some(Arch::X86);
reg
})
.collect()
} else {
Vec::new()
Expand All @@ -145,10 +133,6 @@ pub fn main() -> anyhow::Result<()> {
let xml_conts_regs_x86_64 = include_str!("../../registers/x86_64.xml");
populate_registers(xml_conts_regs_x86_64)?
.into_iter()
.map(|mut reg| {
reg.arch = Some(Arch::X86_64);
reg
})
.collect()
} else {
Vec::new()
Expand All @@ -158,17 +142,32 @@ pub fn main() -> anyhow::Result<()> {
populate_name_to_register_map(Arch::X86, &x86_registers, &mut names_to_registers);
populate_name_to_register_map(Arch::X86_64, &x86_64_registers, &mut names_to_registers);

let gas_directives = if target_config.assemblers.gas {
info!("Populating directive set -> Gas...");
let xml_conts_gas = include_str!("../../directives/gas_directives.xml");
populate_directives(xml_conts_gas)?.into_iter().collect()
} else {
Vec::new()
};

let mut names_to_directives = NameToDirectiveMap::new();
populate_name_to_directive_map(Assembler::Gas, &gas_directives, &mut names_to_directives);

let instr_completion_items =
get_completes(&names_to_instructions, Some(CompletionItemKind::OPERATOR));
let reg_completion_items =
get_completes(&names_to_registers, Some(CompletionItemKind::VARIABLE));
let directive_completion_items =
get_completes(&names_to_directives, Some(CompletionItemKind::OPERATOR));

main_loop(
&connection,
initialization_params,
&names_to_instructions,
&names_to_directives,
&names_to_registers,
&instr_completion_items,
&directive_completion_items,
&reg_completion_items,
)?;
io_threads.join()?;
Expand All @@ -178,12 +177,15 @@ pub fn main() -> anyhow::Result<()> {
Ok(())
}

#[allow(clippy::too_many_arguments)]
fn main_loop(
connection: &Connection,
params: serde_json::Value,
names_to_instructions: &NameToInstructionMap,
names_to_directives: &NameToDirectiveMap,
names_to_registers: &NameToRegisterMap,
instruction_completion_items: &[CompletionItem],
directive_completion_items: &[CompletionItem],
register_completion_items: &[CompletionItem],
) -> anyhow::Result<()> {
let _params: InitializeParams = serde_json::from_value(params).unwrap();
Expand Down Expand Up @@ -214,8 +216,12 @@ fn main_loop(
// format response
match word {
Ok(word) => {
let hover_res =
get_hover_resp(&word, names_to_instructions, names_to_registers);
let hover_res = get_hover_resp(
&word,
names_to_instructions,
names_to_directives,
names_to_registers,
);
match hover_res {
Some(_) => {
let result = serde_json::to_value(&hover_res).unwrap();
Expand Down Expand Up @@ -252,6 +258,7 @@ fn main_loop(
&mut tree,
&params,
instruction_completion_items,
directive_completion_items,
register_completion_items,
);
match comp_res {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ pub mod x86_parser;
pub use lsp::*;
pub use types::*;
pub use x86_parser::{
populate_instructions, populate_name_to_instruction_map, populate_name_to_register_map,
populate_registers,
populate_directives, populate_instructions, populate_name_to_directive_map,
populate_name_to_instruction_map, populate_name_to_register_map, populate_registers,
};
124 changes: 107 additions & 17 deletions src/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::types::Column;
use crate::{Arch, Completable, Hoverable, Instruction, NameToInstructionMap, TargetConfig};
use crate::{
Arch, ArchOrAssembler, Assembler, Completable, Hoverable, Instruction, NameToInstructionMap,
TargetConfig,
};
use dirs::config_dir;
use log::{error, info, log, log_enabled};
use lsp_textdocument::FullTextDocument;
Expand Down Expand Up @@ -129,12 +132,12 @@ pub fn text_doc_change_to_ts_edit(

/// Given a NameTo_SomeItem_ map, returns a `Vec<CompletionItem>` for the items
/// contained within the map
pub fn get_completes<T: Completable>(
map: &HashMap<(Arch, &str), T>,
pub fn get_completes<T: Completable, U: ArchOrAssembler>(
map: &HashMap<(U, &str), T>,
kind: Option<CompletionItemKind>,
) -> Vec<CompletionItem> {
map.iter()
.map(|((_arch, name), item_info)| {
.map(|((_arch_or_asm, name), item_info)| {
let value = format!("{}", item_info);

CompletionItem {
Expand All @@ -150,17 +153,23 @@ pub fn get_completes<T: Completable>(
.collect()
}

pub fn get_hover_resp<T: Hoverable, S: Hoverable>(
pub fn get_hover_resp<T: Hoverable, U: Hoverable, V: Hoverable>(
word: &str,
instruction_map: &HashMap<(Arch, &str), T>,
register_map: &HashMap<(Arch, &str), S>,
directive_map: &HashMap<(Assembler, &str), U>,
register_map: &HashMap<(Arch, &str), V>,
) -> Option<Hover> {
let instr_lookup = lookup_hover_resp(word, instruction_map);
let instr_lookup = lookup_hover_resp_by_arch(word, instruction_map);
if instr_lookup.is_some() {
return instr_lookup;
}

let reg_lookup = lookup_hover_resp(word, register_map);
let directive_lookup = lookup_hover_resp_by_assembler(word, directive_map);
if directive_lookup.is_some() {
return directive_lookup;
}

let reg_lookup = lookup_hover_resp_by_arch(word, register_map);
if reg_lookup.is_some() {
return reg_lookup;
}
Expand All @@ -174,8 +183,11 @@ pub fn get_hover_resp<T: Hoverable, S: Hoverable>(
None
}

fn lookup_hover_resp<T: Hoverable>(word: &str, map: &HashMap<(Arch, &str), T>) -> Option<Hover> {
let (x86_res, x86_64_res) = search_for_hoverable(word, map);
fn lookup_hover_resp_by_arch<T: Hoverable>(
word: &str,
map: &HashMap<(Arch, &str), T>,
) -> Option<Hover> {
let (x86_res, x86_64_res) = search_for_hoverable_by_arch(word, map);

match (x86_res.is_some(), x86_64_res.is_some()) {
(true, _) | (_, true) => {
Expand Down Expand Up @@ -205,6 +217,36 @@ fn lookup_hover_resp<T: Hoverable>(word: &str, map: &HashMap<(Arch, &str), T>) -
}
}

fn lookup_hover_resp_by_assembler<T: Hoverable>(
word: &str,
map: &HashMap<(Assembler, &str), T>,
) -> Option<Hover> {
let (gas_res, go_res) = search_for_hoverable_by_assembler(word, map);

match (gas_res.is_some(), go_res.is_some()) {
(true, _) | (_, true) => {
let mut value = String::new();
if let Some(gas_res) = gas_res {
value += &format!("{}", gas_res);
}
if let Some(go_res) = go_res {
value += &format!("{}{}", if gas_res.is_some() { "\n\n" } else { "" }, go_res);
}
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value,
}),
range: None,
})
}
_ => {
// don't know of this word
None
}
}
}

fn get_demangle_resp(word: &str) -> Option<Hover> {
let name = Name::new(word, NameMangling::Mangled, Language::Unknown);
let demangled = name.demangle(DemangleOptions::complete());
Expand Down Expand Up @@ -255,18 +297,31 @@ pub fn get_comp_resp(
curr_tree: &mut Option<Tree>,
params: &CompletionParams,
instr_comps: &[CompletionItem],
dir_comps: &[CompletionItem],
reg_comps: &[CompletionItem],
) -> Option<CompletionList> {
let cursor_line = params.text_document_position.position.line as usize;
let cursor_char = params.text_document_position.position.character as usize;

// prepend register names with "%" in GAS
if let Some(ctx) = params.context.as_ref() {
if ctx.trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
return Some(CompletionList {
is_incomplete: true,
items: filtered_comp_list(reg_comps),
});
match ctx.trigger_character.as_ref().map(|s| s.as_ref()) {
// prepend GAS registers with "%"
Some("%") => {
return Some(CompletionList {
is_incomplete: true,
items: filtered_comp_list(reg_comps),
});
}
// prepend GAS directives with "."
Some(".") => {
return Some(CompletionList {
is_incomplete: true,
items: filtered_comp_list(dir_comps),
});
}
_ => {}
}
}
}

Expand All @@ -286,6 +341,30 @@ pub fn get_comp_resp(
});
let curr_doc = curr_doc.as_bytes();

static QUERY_DIRECTIVE: Lazy<tree_sitter::Query> = Lazy::new(|| {
tree_sitter::Query::new(
tree_sitter_asm::language(),
"(meta kind: (meta_ident) @directive)",
)
.unwrap()
});
let matches_iter = cursor.matches(&QUERY_DIRECTIVE, tree.root_node(), curr_doc);

for match_ in matches_iter {
let caps = match_.captures;
for cap in caps.iter() {
let arg_start = cap.node.range().start_point;
let arg_end = cap.node.range().end_point;
if cursor_matches!(cursor_line, cursor_char, arg_start, arg_end) {
let items = filtered_comp_list(dir_comps);
return Some(CompletionList {
is_incomplete: true,
items,
});
}
}
}

static QUERY_INSTR_ANY: Lazy<tree_sitter::Query> = Lazy::new(|| {
tree_sitter::Query::new(
tree_sitter_asm::language(),
Expand Down Expand Up @@ -485,7 +564,8 @@ pub fn get_sig_help_resp(
let mut value = String::new();
let mut has_x86 = false;
let mut has_x86_64 = false;
let (x86_info, x86_64_info) = search_for_hoverable(instr_name, instr_info);
let (x86_info, x86_64_info) =
search_for_hoverable_by_arch(instr_name, instr_info);
if let Some(sig) = x86_info {
for form in sig.forms.iter() {
if let Some(ref gas_name) = form.gas_name {
Expand Down Expand Up @@ -691,7 +771,7 @@ pub fn get_ref_resp(
// If issue is resolved, can add a separate lifetime "'b" to "word"
// parameter such that 'a: 'b
// For now, using 'a for both isn't strictly necessary, but fits our use case
fn search_for_hoverable<'a, T: Hoverable>(
fn search_for_hoverable_by_arch<'a, T: Hoverable>(
word: &'a str,
map: &'a HashMap<(Arch, &str), T>,
) -> (Option<&'a T>, Option<&'a T>) {
Expand All @@ -701,6 +781,16 @@ fn search_for_hoverable<'a, T: Hoverable>(
(x86_res, x86_64_res)
}

fn search_for_hoverable_by_assembler<'a, T: Hoverable>(
word: &'a str,
map: &'a HashMap<(Assembler, &str), T>,
) -> (Option<&'a T>, Option<&'a T>) {
let gas_res = map.get(&(Assembler::Gas, word));
let go_res = map.get(&(Assembler::Go, word));

(gas_res, go_res)
}

/// Searches for global config in ~/.config/asm-lsp, then the project's directory
/// Project specific configs will override global configs
pub fn get_target_config(params: &InitializeParams) -> TargetConfig {
Expand Down
Loading

0 comments on commit 8e419d6

Please sign in to comment.