Skip to content

Commit

Permalink
feat: Hover and Autocomplete for gas directives (#57)
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

Fixed get word bug
  • Loading branch information
WillLillis committed Jun 29, 2024
1 parent 1816817 commit 0e63cfd
Show file tree
Hide file tree
Showing 6 changed files with 1,200 additions and 21 deletions.
852 changes: 852 additions & 0 deletions directives/gas_directives.xml

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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 @@ -171,19 +171,34 @@ pub fn main() -> anyhow::Result<()> {
populate_name_to_register_map(Arch::X86_64, &x86_64_registers, &mut names_to_registers);
populate_name_to_register_map(Arch::Z80, &z80_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));

let include_dirs = get_include_dirs();

main_loop(
&connection,
initialization_params,
&names_to_instructions,
&names_to_directives,
&names_to_registers,
&instr_completion_items,
&directive_completion_items,
&reg_completion_items,
&include_dirs,
)?;
Expand All @@ -194,12 +209,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],
include_dirs: &[PathBuf],
) -> anyhow::Result<()> {
Expand Down Expand Up @@ -250,6 +268,7 @@ fn main_loop(
file_word,
names_to_instructions,
names_to_registers,
names_to_directives,
include_dirs,
);
match hover_res {
Expand Down Expand Up @@ -282,6 +301,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: 106 additions & 18 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 @@ -192,12 +195,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 @@ -213,25 +216,30 @@ 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,
file_word: &str,
instruction_map: &HashMap<(Arch, &str), T>,
register_map: &HashMap<(Arch, &str), S>,
register_map: &HashMap<(Arch, &str), U>,
directive_map: &HashMap<(Assembler, &str), V>,
include_dirs: &[PathBuf],
) -> 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;
}

let demang = get_demangle_resp(word);

if demang.is_some() {
return demang;
}
Expand All @@ -244,9 +252,12 @@ 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> {
fn lookup_hover_resp_by_arch<T: Hoverable>(
word: &str,
map: &HashMap<(Arch, &str), T>,
) -> Option<Hover> {
// switch over to vec?
let (x86_res, x86_64_res, z80_res) = search_for_hoverable(word, map);
let (x86_res, x86_64_res, z80_res) = search_for_hoverable_by_arch(word, map);
match (x86_res.is_some(), x86_64_res.is_some(), z80_res.is_some()) {
(true, _, _) | (_, true, _) | (_, _, true) => {
let mut value = String::new();
Expand Down Expand Up @@ -278,6 +289,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 @@ -371,18 +412,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 @@ -402,6 +456,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 @@ -602,7 +680,7 @@ pub fn get_sig_help_resp(
let mut has_x86_64 = false;
let mut has_z80 = false;
let (x86_info, x86_64_info, z80_info) =
search_for_hoverable(instr_name, instr_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 @@ -821,7 +899,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>, Option<&'a T>) {
Expand All @@ -832,6 +910,16 @@ fn search_for_hoverable<'a, T: Hoverable>(
(x86_res, x86_64_res, z80_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 0e63cfd

Please sign in to comment.