From 2943e33e3bf18dfde262dc6e68b13c758f26632d Mon Sep 17 00:00:00 2001 From: Will Lillis Date: Sun, 1 Oct 2023 05:03:37 -0400 Subject: [PATCH] Specify asm-lsp target configuration via config file. Enable specifying which assemblers and which architectures to be enabled/disabled when interacting with the LSP server. Use this feature to e.g., disable showing the `go` opcode documentation or to show only the documentation of the x86_64 architecture. Closes #4 --- .gitignore | 2 ++ Cargo.lock | 79 ++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + README.md | 17 +++++++++ src/bin/main.rs | 92 +++++++++++++++++++++++++++++++------------------ src/lsp.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++- src/types.rs | 48 ++++++++++++++++++++++++++ 7 files changed, 291 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 3e05b50..fbf50dd 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,5 @@ tags # End of https://www.toptal.com/developers/gitignore/api/rust,vim,python rusty-tags.vi + +/.asm-lsp.toml diff --git a/Cargo.lock b/Cargo.lock index 602ae7c..819205f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", + "toml", ] [[package]] @@ -187,6 +188,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.3" @@ -332,7 +339,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -345,6 +352,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -468,7 +481,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -920,6 +943,15 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1090,6 +1122,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1349,6 +1415,15 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 57a2905..e138cac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,5 +43,6 @@ strum = "0.24.1" strum_macros = "0.24.3" serde_json = "1.0.94" serde = "1.0.158" +toml = "0.8.1" # [dev-dependencies] diff --git a/README.md b/README.md index 2cf442f..eedcc1f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,23 @@ Add a section like the following in your `settings.json` file: } ``` +### [OPTIONAL] Configure via `.asm-lsp.toml` + +Add a `.asm-lsp.toml` file like the following to your project's root directory +to selectively target specific assemblers and/or instruction sets: + +```toml +version = "0.1" + +[assemblers] +gas = true +go = false + +[instruction_sets] +x86 = false +x86_64 = true +``` + ## Demo ### Hovering / Documentation support diff --git a/src/bin/main.rs b/src/bin/main.rs index b54e857..105b5ed 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -4,6 +4,8 @@ use log::{error, info}; use lsp_types::request::HoverRequest; use lsp_types::*; +use crate::lsp::{filter_targets, get_target_config}; + use lsp_server::{Connection, Message, Request, RequestId, Response}; use serde_json::json; @@ -14,39 +16,8 @@ pub fn main() -> anyhow::Result<()> { // logging only write out to stderr. flexi_logger::Logger::try_with_str("info")?.start()?; - // create a map of &Instruction_name -> &Instruction - Use that in user queries - // The Instruction(s) themselves are stored in a vector and we only keep references to the - // former map - info!("Populating instruction set -> x86..."); - let xml_conts_x86 = include_str!("../../opcodes/x86.xml"); - let x86_instructions = populate_instructions(xml_conts_x86)? - .into_iter() - .map(|mut instruction| { - instruction.arch = Some(Arch::X86); - instruction - }) - .collect(); - - info!("Populating instruction set -> x86_64..."); - let xml_conts_x86_64 = include_str!("../../opcodes/x86_64.xml"); - let x86_64_instructions = populate_instructions(xml_conts_x86_64)? - .into_iter() - .map(|mut instruction| { - instruction.arch = Some(Arch::X86_64); - instruction - }) - .collect(); - - let mut names_to_instructions = NameToInstructionMap::new(); - populate_name_to_instruction_map(Arch::X86, &x86_instructions, &mut names_to_instructions); - populate_name_to_instruction_map( - Arch::X86_64, - &x86_64_instructions, - &mut names_to_instructions, - ); - // LSP server initialisation ------------------------------------------------------------------ - info!("Starting lsp server..."); + info!("Starting LSP server..."); // Create the transport let (connection, io_threads) = Connection::stdio(); @@ -57,13 +28,66 @@ pub fn main() -> anyhow::Result<()> { hover_provider, ..ServerCapabilities::default() }; - let server_capabilities = serde_json::to_value(&capabilities).unwrap(); + let server_capabilities = serde_json::to_value(capabilities).unwrap(); let initialization_params = connection.initialize(server_capabilities)?; + + let params: InitializeParams = serde_json::from_value(initialization_params.clone()).unwrap(); + let target_config = get_target_config(¶ms); + + // create a map of &Instruction_name -> &Instruction - Use that in user queries + // The Instruction(s) themselves are stored in a vector and we only keep references to the + // former map + let x86_instructions = if target_config.instruction_sets.x86 { + info!("Populating instruction set -> x86..."); + 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 + filter_targets(&instruction, &target_config) + }) + .filter(|instruction| !instruction.forms.is_empty()) + .collect() + } else { + Vec::new() + }; + + let x86_64_instructions = if target_config.instruction_sets.x86_64 { + info!("Populating instruction set -> x86_64..."); + 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 + filter_targets(&instruction, &target_config) + }) + .filter(|instruction| !instruction.forms.is_empty()) + .collect() + } else { + Vec::new() + }; + + let mut names_to_instructions = NameToInstructionMap::new(); + populate_name_to_instruction_map(Arch::X86, &x86_instructions, &mut names_to_instructions); + populate_name_to_instruction_map( + Arch::X86_64, + &x86_64_instructions, + &mut names_to_instructions, + ); + main_loop(&connection, initialization_params, &names_to_instructions)?; io_threads.join()?; // Shut down gracefully. - info!("Shutting down lsp server"); + info!("Shutting down LSP server"); Ok(()) } diff --git a/src/lsp.rs b/src/lsp.rs index a6dafd9..9d2f319 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -1,7 +1,11 @@ use crate::types::Column; -use lsp_types::TextDocumentPositionParams; +use crate::{Instruction, TargetConfig}; +use log::{error, info}; +use lsp_types::{InitializeParams, TextDocumentPositionParams, Url}; use std::fs::File; use std::io::BufRead; +use std::path::PathBuf; + /// Find the start and end indices of a word inside the given line /// Borrowed from RLS pub fn find_word_at_pos(line: &str, col: Column) -> (Column, Column) { @@ -48,3 +52,86 @@ pub fn get_word_from_file_params( Err(_) => Err(anyhow::anyhow!("filepath get error")), } } + +pub fn get_target_config(params: &InitializeParams) -> TargetConfig { + // 1. if we have workspace folders, then iterate through them and assign the first valid one to + // the root path + // 2. If we don't have worksace folders or none of them is a valid path, check the root_uri + // variable + // 3. If we do have a root_path, check whether we can find a .asm-lsp file at its root. + // 4. If everything fails return TargetConfig::default() + + let mut root_path: Option = None; + + // first check workspace folders + if let Some(folders) = ¶ms.workspace_folders { + // if there's multiple, just visit in order until we find a valid folder + let mut path = None; + for folder in folders.iter() { + if let Ok(parsed) = Url::parse(folder.uri.as_str()) { + if let Ok(parsed_path) = parsed.to_file_path() { + path = Some(parsed_path); + break; + } + } + } + + root_path = path; + }; + + // if workspace folders weren't set or came up empty, we check the root_uri + if root_path.is_none() { + if let Some(root_uri) = ¶ms.root_uri { + if let Ok(path) = root_uri.to_file_path() { + root_path = Some(path) + } + } + }; + + // if we have a properly configured root path, check for the config file + if let Some(mut path) = root_path { + path.push(".asm-lsp.toml"); + if let Ok(config) = std::fs::read_to_string(path.clone()) { + let path_s = path.display(); + match toml::from_str::(&config) { + Ok(config) => { + info!("Parsing asm-lsp config from file -> {path_s}\n"); + return config; + } + Err(e) => { + error!("Failed to parse config file {path_s} - Error: {e}\n"); + } // if there's an error we fall through to the default + } + } + } + + // default is to turn everything on + TargetConfig::default() +} + +pub fn filter_targets(instr: &Instruction, config: &TargetConfig) -> Instruction { + let mut instr = instr.clone(); + + let forms = instr + .forms + .iter() + .filter(|form| { + (form.gas_name.is_some() && config.assemblers.gas) + || (form.go_name.is_some() && config.assemblers.go) + }) + .map(|form| { + let mut filtered = form.clone(); + // handle cases where gas and go both have names on the same form + if !config.assemblers.gas { + filtered.gas_name = None; + } + if !config.assemblers.go { + filtered.go_name = None; + } + filtered + }) + .collect(); + + instr.forms = forms; + instr +} diff --git a/src/types.rs b/src/types.rs index b5be06c..c921ef2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use strum_macros::{AsRefStr, EnumString}; @@ -178,6 +179,53 @@ pub enum Arch { X86_64, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Assemblers { + pub gas: bool, + pub go: bool, +} + +impl Default for Assemblers { + fn default() -> Self { + Assemblers { + gas: true, + go: true, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstructionSets { + pub x86: bool, + pub x86_64: bool, +} + +impl Default for InstructionSets { + fn default() -> Self { + InstructionSets { + x86: true, + x86_64: true, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TargetConfig { + pub version: String, + pub assemblers: Assemblers, + pub instruction_sets: InstructionSets, +} + +impl Default for TargetConfig { + fn default() -> Self { + TargetConfig { + version: String::from("0.1"), + assemblers: Assemblers::default(), + instruction_sets: InstructionSets::default(), + } + } +} + // Instruction Set Architecture ------------------------------------------------------------------- #[derive(Debug, Clone, EnumString, AsRefStr)] pub enum ISA {