mirror of
https://github.com/bergercookie/asm-lsp.git
synced 2025-12-23 12:26:44 +00:00
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
This commit is contained in:
parent
9914861af8
commit
2943e33e3b
7 changed files with 291 additions and 37 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -167,3 +167,5 @@ tags
|
|||
# End of https://www.toptal.com/developers/gitignore/api/rust,vim,python
|
||||
|
||||
rusty-tags.vi
|
||||
|
||||
/.asm-lsp.toml
|
||||
|
|
|
|||
79
Cargo.lock
generated
79
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
17
README.md
17
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
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
89
src/lsp.rs
89
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<PathBuf> = 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::<TargetConfig>(&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
|
||||
}
|
||||
|
|
|
|||
48
src/types.rs
48
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue