aboutsummaryrefslogtreecommitdiff
path: root/rust-analyzer-chromiumos-wrapper/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust-analyzer-chromiumos-wrapper/src/main.rs')
-rw-r--r--rust-analyzer-chromiumos-wrapper/src/main.rs222
1 files changed, 144 insertions, 78 deletions
diff --git a/rust-analyzer-chromiumos-wrapper/src/main.rs b/rust-analyzer-chromiumos-wrapper/src/main.rs
index 43ca5a3d..b55623b5 100644
--- a/rust-analyzer-chromiumos-wrapper/src/main.rs
+++ b/rust-analyzer-chromiumos-wrapper/src/main.rs
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::collections::HashMap;
use std::env;
+use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::os::unix::process::CommandExt;
@@ -13,16 +13,15 @@ use std::str::from_utf8;
use std::thread;
use anyhow::{anyhow, bail, Context, Result};
-use lazy_static::lazy_static;
-use log::trace;
-
-use regex::Regex;
+use log::{trace, warn};
use simplelog::{Config, LevelFilter, WriteLogger};
use serde_json::{from_slice, to_writer, Value};
+use url::Url;
-const CHROOT_SERVER_PATH: &str = "/usr/sbin/rust-analyzer";
+const SERVER_FILENAME: &str = "rust-analyzer-chromiumos-wrapper";
+const CHROOT_SERVER_PATH: &str = "/usr/bin/rust-analyzer";
fn main() -> Result<()> {
let args = env::args().skip(1);
@@ -122,16 +121,14 @@ fn main() -> Result<()> {
let mut child_stdin = BufWriter::new(child.0.stdin.take().unwrap());
let mut child_stdout = BufReader::new(child.0.stdout.take().unwrap());
- let replacement_map = {
- let mut m = HashMap::new();
- m.insert(outside_prefix, inside_prefix);
- m.insert(outside_sysroot_prefix, "/usr/lib/rustlib");
- m.insert(outside_home, "/home");
- m
- };
+ let replacement_map = [
+ (outside_prefix, inside_prefix),
+ (outside_sysroot_prefix, "/usr/lib/rustlib"),
+ (outside_home, "/home"),
+ ];
let join_handle = {
- let rm = replacement_map.clone();
+ let rm = replacement_map;
thread::spawn(move || {
let mut stdin = io::stdin().lock();
stream_with_replacement(&mut stdin, &mut child_stdin, &rm)
@@ -140,7 +137,7 @@ fn main() -> Result<()> {
};
// For the mapping between inside to outside, we just reverse the map.
- let replacement_map_rev = replacement_map.iter().map(|(k, v)| (*v, *k)).collect();
+ let replacement_map_rev = replacement_map.map(|(k, v)| (v, k));
let mut stdout = BufWriter::new(io::stdout().lock());
stream_with_replacement(&mut child_stdout, &mut stdout, &replacement_map_rev)
.context("Streaming from rust-analyzer into stdout")?;
@@ -207,32 +204,82 @@ fn read_header<R: BufRead>(r: &mut R, header: &mut Header) -> Result<()> {
}
}
+// The url crate's percent decoding helper returns a Path, while for non-url strings we don't
+// want to decode all of them as a Path since most of them are non-path strings.
+// We opt for not sharing the code paths as the handling of plain strings and Paths are slightly
+// different (notably that Path normalizes away trailing slashes), but otherwise the two functions
+// are functionally equal.
+fn replace_uri(s: &str, replacement_map: &[(&str, &str)]) -> Result<String> {
+ let uri = Url::parse(s).with_context(|| format!("while parsing path {s:?}"))?;
+ let is_dir = uri.as_str().ends_with('/');
+ let path = uri
+ .to_file_path()
+ .map_err(|()| anyhow!("while converting {s:?} to file path"))?;
+
+ // Always replace the server path everywhere.
+ if path.file_name() == Some(OsStr::new(SERVER_FILENAME)) {
+ return Ok(CHROOT_SERVER_PATH.into());
+ }
+
+ fn path_to_url(path: &Path, is_dir: bool) -> Result<String> {
+ let url = if is_dir {
+ Url::from_directory_path(path)
+ } else {
+ Url::from_file_path(path)
+ };
+ url.map_err(|()| anyhow!("while converting {path:?} to url"))
+ .map(|p| p.into())
+ }
+
+ // Replace by the first prefix match.
+ for (pattern, replacement) in replacement_map {
+ if let Ok(rest) = path.strip_prefix(pattern) {
+ let new_path = Path::new(replacement).join(rest);
+ return path_to_url(&new_path, is_dir);
+ }
+ }
+
+ Ok(s.into())
+}
+
+fn replace_path(s: &str, replacement_map: &[(&str, &str)]) -> String {
+ // Always replace the server path everywhere.
+ if s.strip_suffix(SERVER_FILENAME)
+ .is_some_and(|s| s.ends_with('/'))
+ {
+ return CHROOT_SERVER_PATH.into();
+ }
+
+ // Replace by the first prefix match.
+ for (pattern, replacement) in replacement_map {
+ if let Some(rest) = s.strip_prefix(pattern) {
+ if rest.is_empty() || rest.starts_with('/') {
+ return [replacement, rest].concat();
+ }
+ }
+ }
+
+ s.into()
+}
+
/// Extend `dest` with `contents`, replacing any occurrence of patterns in a json string in
/// `contents` with a replacement.
-fn replace(
- contents: &[u8],
- replacement_map: &HashMap<&str, &str>,
- dest: &mut Vec<u8>,
-) -> Result<()> {
- fn map_value(val: Value, replacement_map: &HashMap<&str, &str>) -> Value {
+fn replace(contents: &[u8], replacement_map: &[(&str, &str)], dest: &mut Vec<u8>) -> Result<()> {
+ fn map_value(val: Value, replacement_map: &[(&str, &str)]) -> Value {
match val {
- Value::String(s) =>
- // `s.replace` is very likely doing more work than necessary. Probably we only need
- // to look for the pattern at the beginning of the string.
- {
- lazy_static! {
- static ref SERVER_PATH_REGEX: Regex =
- Regex::new(r".*/rust-analyzer-chromiumos-wrapper$").unwrap();
+ Value::String(mut s) => {
+ if s.starts_with("file:") {
+ // rust-analyzer uses LSP paths most of the time, which are encoded with the
+ // file: URL scheme.
+ s = replace_uri(&s, replacement_map).unwrap_or_else(|e| {
+ warn!("replace_uri failed: {e:?}");
+ s
+ });
+ } else {
+ // For certain config items, paths may be used instead of URIs.
+ s = replace_path(&s, replacement_map);
}
- // Always replace the server path everywhere.
- let mut s = SERVER_PATH_REGEX
- .replace_all(&s, CHROOT_SERVER_PATH)
- .to_string();
- // Then replace all mappings we get.
- for (pattern, replacement) in replacement_map {
- s = s.replace(pattern, replacement);
- }
- Value::String(s.to_string())
+ Value::String(s)
}
Value::Array(mut v) => {
for val_ref in v.iter_mut() {
@@ -271,7 +318,7 @@ fn replace(
fn stream_with_replacement<R: BufRead, W: Write>(
r: &mut R,
w: &mut W,
- replacement_map: &HashMap<&str, &str>,
+ replacement_map: &[(&str, &str)],
) -> Result<()> {
let mut head = Header::default();
let mut buf = Vec::with_capacity(1024);
@@ -350,17 +397,12 @@ mod test {
fn test_stream_with_replacement(
read: &str,
- pattern: &str,
- replacement: &str,
+ replacement_map: &[(&str, &str)],
json_expected: &str,
) -> Result<()> {
- let mut w = Vec::<u8>::with_capacity(read.len());
- let replacement_map = {
- let mut m = HashMap::new();
- m.insert(pattern, replacement);
- m
- };
- stream_with_replacement(&mut read.as_bytes(), &mut w, &replacement_map)?;
+ let mut w = Vec::new();
+ let input = format!("Content-Length: {}\r\n\r\n{}", read.as_bytes().len(), read);
+ stream_with_replacement(&mut input.as_bytes(), &mut w, &replacement_map)?;
// serde_json may not format the json output the same as we do, so we can't just compare
// as strings or slices.
@@ -384,48 +426,72 @@ mod test {
}
#[test]
- fn test_stream_with_replacement_1() -> Result<()> {
+ fn test_stream_with_replacement_simple() -> Result<()> {
+ test_stream_with_replacement(
+ r#"{
+ "somekey": {
+ "somepath": "/XYZXYZ/",
+ "anotherpath": "/some/string"
+ },
+ "anotherkey": "/XYZXYZ/def"
+ }"#,
+ &[("/XYZXYZ", "/REPLACE")],
+ r#"{
+ "somekey": {
+ "somepath": "/REPLACE/",
+ "anotherpath": "/some/string"
+ },
+ "anotherkey": "/REPLACE/def"
+ }"#,
+ )
+ }
+
+ #[test]
+ fn test_stream_with_replacement_file_uri() -> Result<()> {
test_stream_with_replacement(
- // read
- "Content-Length: 93\r\n\r\n{\"somekey\": {\"somepath\": \"XYZXYZabc\",\
- \"anotherpath\": \"somestring\"}, \"anotherkey\": \"XYZXYZdef\"}",
- // pattern
- "XYZXYZ",
- // replacement
- "REPLACE",
- // json_expected
- "{\"somekey\": {\"somepath\": \"REPLACEabc\", \"anotherpath\": \"somestring\"},\
- \"anotherkey\": \"REPLACEdef\"}",
+ r#"{
+ "key0": "file:///ABCDEF/",
+ "key1": {
+ "key2": 5,
+ "key3": "file:///ABCDEF/text"
+ },
+ "key4": 1
+ }"#,
+ &[("/ABCDEF", "/replacement")],
+ r#"{
+ "key0": "file:///replacement/",
+ "key1": {
+ "key2": 5,
+ "key3": "file:///replacement/text"
+ },
+ "key4": 1
+ }"#,
)
}
#[test]
- fn test_stream_with_replacement_2() -> Result<()> {
+ fn test_stream_with_replacement_self_binary() -> Result<()> {
test_stream_with_replacement(
- // read
- "Content-Length: 83\r\n\r\n{\"key0\": \"sometextABCDEF\",\
- \"key1\": {\"key2\": 5, \"key3\": \"moreABCDEFtext\"}, \"key4\": 1}",
- // pattern
- "ABCDEF",
- // replacement
- "replacement",
- // json_expected
- "{\"key0\": \"sometextreplacement\", \"key1\": {\"key2\": 5,\
- \"key3\": \"morereplacementtext\"}, \"key4\": 1}",
+ r#"{
+ "path": "/my_folder/rust-analyzer-chromiumos-wrapper"
+ }"#,
+ &[],
+ r#"{
+ "path": "/usr/bin/rust-analyzer"
+ }"#,
)
}
#[test]
- fn test_stream_with_replacement_3() -> Result<()> {
+ fn test_stream_with_replacement_replace_once() -> Result<()> {
test_stream_with_replacement(
- // read
- "Content-Length: 55\r\n\r\n{\"path\": \"/my_folder/rust-analyzer-chromiumos-wrapper\"}",
- // pattern
- "",
- // replacement
- "",
- // json_expected
- "{\"path\": \"/usr/sbin/rust-analyzer\"}",
+ r#"{
+ "path": "/mnt/home/file"
+ }"#,
+ &[("/mnt/home", "/home"), ("/home", "/foo")],
+ r#"{
+ "path": "/home/file"
+ }"#,
)
}
}