From f5334c04569d5d6339f7989bd3ffed3fcb2f8f7c Mon Sep 17 00:00:00 2001 From: Robin Bozan Date: Tue, 18 Jan 2022 16:03:38 +0100 Subject: [PATCH] feat: Shows all capture groups in table, closes #40 (#41) --- src/main.rs | 146 ++++++++++++++++++--------- src/tests/definition.rs | 15 ++- src/tests/helpers.rs | 3 + src/tests/hover.rs | 73 +++++++++++++- src/tests/hover_per_language_file.rs | 7 +- src/tree_sitter_helper.rs | 27 ++++- 6 files changed, 215 insertions(+), 56 deletions(-) diff --git a/src/main.rs b/src/main.rs index a45fc1a..d14a1ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,8 @@ use lsp_document::{IndexedText, TextAdapter, TextMap}; mod string_helper; use crate::string_helper::find_translation_key_by_position; use country_emoji::flag; - +use std::collections::HashMap; +use std::convert::TryInto; use std::path::Path; use string_helper::get_editing_range; use string_helper::TRANSLATION_BEGIN_CHARS; @@ -267,19 +268,17 @@ impl Backend { .translation_files .include .iter() - .map(|pattern| { - FileSystemWatcher { - glob_pattern: path_clean::clean( - folder - .uri - .to_file_path() - .unwrap() - .join(PathBuf::from(pattern)) - .to_str() - .unwrap(), - ), - kind: None, - } + .map(|pattern| FileSystemWatcher { + glob_pattern: path_clean::clean( + folder + .uri + .to_file_path() + .unwrap() + .join(PathBuf::from(pattern)) + .to_str() + .unwrap(), + ), + kind: None, }) .collect::>() }) @@ -340,7 +339,10 @@ impl Backend { match new_definitions_result { Some(mut new_definitions) => { // Use file regex language for all above definitions - let language = self + let mut extra_data = HashMap::::new(); + + // Use file regex language for all above definitions + if let Some(file_name_details_regex) = self .config .lock() .unwrap() @@ -348,18 +350,26 @@ impl Backend { .file_name .details .as_ref() - .and_then(|file_name_details_regex| { - file_name_details_regex - .captures(path.file_name().unwrap().to_str().unwrap()) - .and_then(|cap| { - cap.name("language") - .map(|matches| matches.as_str().to_string()) - }) - }); + { + if let Some(cap) = + file_name_details_regex.captures(path.file_name().unwrap().to_str().unwrap()) + { + for capture_group_name in file_name_details_regex.capture_names().flatten() { + let capture_group_result = cap.name(capture_group_name); + + if capture_group_result.is_some() { + extra_data.insert( + capture_group_name.to_string(), + capture_group_result.unwrap().as_str().to_string(), + ); + } + } + }; + }; let translation_file = DefinitionSource { path: path.to_path_buf(), - language, + extra_data, }; for definition in new_definitions.iter_mut() { @@ -396,29 +406,72 @@ impl Backend { .clone() .any(|definition| definition.get_flag().is_some()); + let extra_data_keys: Vec<&String> = definitions_same_key + .clone() + .flat_map(|definition| { + [ + definition + .extra_data + .keys() + .into_iter() + .collect::>(), + definition.file.as_ref().map_or([].into(), |file| { + file.extra_data.keys().into_iter().collect() + }), + ] + }) + .flatten() + .filter(|key| *key != &"language".to_string()) + .unique() + .collect(); + + let mut table_headers = Vec::new(); + if has_flag || has_language { + table_headers.push("flag"); + table_headers.push("language"); + } + + extra_data_keys.iter().for_each(|data_key| { + table_headers.push(&data_key); + }); + + table_headers.push("translation"); + let body = definitions_same_key .map(|def| { + let mut row_data = Vec::::new(); if has_flag || has_language { - let row_data = vec![ - def.get_flag().unwrap_or("🏴󠁢󠁳󠁢󠁰󠁿".to_string()), - format!("**{}**", def.get_language().unwrap_or(&"".to_string())), - def.get_printable_value(), - ]; - - row_data.join("|") - } else { - format!("|{}", def.get_printable_value()) + row_data.push(def.get_flag().unwrap_or("🏴󠁢󠁳󠁢󠁰󠁿".to_string())); + + row_data.push(format!( + "**{}**", + def.get_language().unwrap_or(&"-".to_string()) + )); } + + extra_data_keys.iter().for_each(|data_key| { + row_data.push( + def.get_full_extra_data(*data_key) + .unwrap_or(&"-".to_string()) + .to_string(), + ); + }); + + row_data.push(def.get_printable_value()); + + row_data.join("|") }) .intersperse("\n".to_string()) .collect::(); - let header = if has_flag || has_language { - "flag|language|translation\n-|-|-" - } else { - "|translation|\n|-" - }; - return Some(format!("{}\n{}", header, body)); + let table_separators: String = table_headers.iter().map(|_| "-").join("|"); + + return Some(format!( + "|{}|\n|{}|\n|{}|", + table_headers.join("|"), + table_separators, + body + )); } None } @@ -679,7 +732,7 @@ async fn main() { #[derive(Debug, Clone)] struct DefinitionSource { path: PathBuf, - language: Option, + extra_data: HashMap, } #[derive(Default, Debug)] @@ -687,8 +740,8 @@ pub struct Definition { key: String, cleaned_key: Option, file: Option, - language: Option, value: String, + extra_data: HashMap, } impl PartialEq for Definition { @@ -715,11 +768,14 @@ impl Definition { self.cleaned_key.as_ref().unwrap_or(&self.key) } + fn get_full_extra_data(&self, key: &str) -> Option<&String> { + self.extra_data + .get(key) + .or(self.file.as_ref().and_then(|file| file.extra_data.get(key))) + } + fn get_language(&self) -> Option<&String> { - return self - .language - .as_ref() - .or(self.file.as_ref().and_then(|file| file.language.as_ref())); + self.get_full_extra_data("language") } /// Returns a flag emoji based on the supplied `language` diff --git a/src/tests/definition.rs b/src/tests/definition.rs index ed71743..9abf85e 100644 --- a/src/tests/definition.rs +++ b/src/tests/definition.rs @@ -2,10 +2,13 @@ use super::*; #[test] fn converts_en_to_us_flag() { + let mut extra_data = HashMap::::new(); + extra_data.insert("language".to_string(), "en".to_string()); + let definition = Definition { key: "en.test_key".to_string(), cleaned_key: Some("test_key".to_string()), - language: Some("en".to_string()), + extra_data, value: "some value".to_string(), ..Default::default() }; @@ -15,10 +18,13 @@ fn converts_en_to_us_flag() { #[test] fn printable_value_escapes_newlines() { + let mut extra_data = HashMap::::new(); + extra_data.insert("language".to_string(), "en".to_string()); + let definition = Definition { key: "en.test_key".to_string(), cleaned_key: Some("test_key".to_string()), - language: Some("en".to_string()), + extra_data, value: "\nSome value with multiple\nnewlines".to_string(), ..Default::default() }; @@ -31,10 +37,13 @@ fn printable_value_escapes_newlines() { #[test] fn printable_value_escapes_vertical_line() { + let mut extra_data = HashMap::::new(); + extra_data.insert("language".to_string(), "en".to_string()); + let definition = Definition { key: "en.test_key".to_string(), cleaned_key: Some("test_key".to_string()), - language: Some("en".to_string()), + extra_data, value: "Abc|defg".to_string(), ..Default::default() }; diff --git a/src/tests/helpers.rs b/src/tests/helpers.rs index f75d26d..be06e6d 100644 --- a/src/tests/helpers.rs +++ b/src/tests/helpers.rs @@ -12,6 +12,9 @@ use futures::select; use futures::{FutureExt, StreamExt}; use std::env; +#[cfg(test)] +use pretty_assertions::assert_eq; + lazy_static! { static ref INITIALIZE_REQUEST: Incoming = serde_json::from_str( r#"{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":1}"# diff --git a/src/tests/hover.rs b/src/tests/hover.rs index c991332..1049f08 100644 --- a/src/tests/hover.rs +++ b/src/tests/hover.rs @@ -3,7 +3,8 @@ use tower_lsp::jsonrpc::{Incoming, Outgoing, Response}; mod helpers; use helpers::*; -// use helpers; +#[cfg(test)] +use pretty_assertions::assert_eq; lazy_static! { static ref DID_OPEN_REQUEST: Incoming = serde_json::from_str( @@ -47,7 +48,7 @@ lazy_static! { { "jsonrpc":"2.0", "result":{ - "contents":"flag|language|translation\n-|-|-\n🇺🇸|**en-us**|This title will appear in the header.", + "contents":"|flag|language|translation|\n|-|-|-|\n|🇺🇸|**en-us**|This title will appear in the header.|", "range":{ "end":{ "character":28, @@ -100,7 +101,7 @@ lazy_static! { ] }, "fileName": { - "details": "testdsddasdasdddsad" + "details": "invalid-details-regex" }, "key": { "filter": "^.+?\\..+?\\.(.+$)" @@ -119,7 +120,7 @@ lazy_static! { { "jsonrpc":"2.0", "result":{ - "contents": "|translation|\n|-\n|This title will appear in the header.", + "contents": "|translation|\n|-|\n|This title will appear in the header.|", "range":{ "end":{ "character":28, @@ -138,6 +139,56 @@ lazy_static! { .unwrap() ); + static ref WORKSPACE_CONFIGURATION_REQUEST_WITH_CUSTOM_DATA : Incoming = serde_json::from_str( + r#"{"jsonrpc":"2.0","result": [ + { + "translationFiles": { + "include": [ + "./fixtures/*.json", + ".\\fixtures\\*.json" + ] + }, + "fileName": { + "details": "^.+?\\.(?P.*?)$" + }, + "key": { + "filter": "^.+?\\..+?\\.(.+$)" + }, + "trace": { + "server": "verbose" + } + } +], "id": 1 }"# + ) + .unwrap(); + + + static ref HOVER_RESPONSE_WITH_CUSTOM_DATA: Outgoing = Outgoing::Response( + serde_json::from_str( + r#" +{ + "jsonrpc":"2.0", + "result":{ + "contents": "|extension|translation|\n|-|-|\n|json|This title will appear in the header.|", + "range":{ + "end":{ + "character":28, + "line":0 + }, + "start":{ + "character":11, + "line":0 + } + } + }, + "id":1 +} +"# + ) + .unwrap() + ); + + } @@ -183,3 +234,17 @@ async fn hover_without_flag_and_language() { Ok(Some(HOVER_RESPONSE_WITHOUT_LANGUAGE.clone())) ); } + +#[tokio::test] +#[timeout(500)] +async fn hover_with_custom_data() { + let (mut service, _) = + prepare_with_workspace_config(&WORKSPACE_CONFIGURATION_REQUEST_WITH_CUSTOM_DATA).await; + + assert_eq!(service.call(DID_OPEN_REQUEST.clone()).await, Ok(None)); + + assert_eq!( + service.call(HOVER_REQUEST.clone()).await, + Ok(Some(HOVER_RESPONSE_WITH_CUSTOM_DATA.clone())) + ); +} diff --git a/src/tests/hover_per_language_file.rs b/src/tests/hover_per_language_file.rs index f765917..73a4209 100644 --- a/src/tests/hover_per_language_file.rs +++ b/src/tests/hover_per_language_file.rs @@ -3,7 +3,8 @@ use tower_lsp::jsonrpc::{Incoming, Outgoing}; mod helpers; use helpers::*; -// use helpers; +#[cfg(test)] +use pretty_assertions::assert_eq; lazy_static! { static ref DID_OPEN_REQUEST: Incoming = serde_json::from_str( @@ -66,7 +67,7 @@ lazy_static! { { "jsonrpc":"2.0", "result":{ - "contents":"flag|language|translation\n-|-|-\n🇳🇱|**nl**|Nederlands\n🇺🇸|**en**|English", + "contents":"|flag|language|translation|\n|-|-|-|\n|🇳🇱|**nl**|Nederlands\n🇺🇸|**en**|English|", "range":{ "end":{ "character": 15, @@ -115,7 +116,7 @@ lazy_static! { { "jsonrpc":"2.0", "result":{ - "contents": "|translation|\n|-\n|Nederlands\n|English", + "contents": "|translation|\n|-|\n|Nederlands\nEnglish|", "range":{ "end":{ "character": 15, diff --git a/src/tree_sitter_helper.rs b/src/tree_sitter_helper.rs index e8fc1b5..c2c1f13 100644 --- a/src/tree_sitter_helper.rs +++ b/src/tree_sitter_helper.rs @@ -1,4 +1,5 @@ use std::ops::Range; +use std::collections::HashMap; use crate::{Definition, ExtensionConfig}; use tree_sitter::{Language, Node, Parser, Query, QueryCursor, QueryMatches}; @@ -90,7 +91,7 @@ pub fn parse_translation_structure( key: path.clone(), cleaned_key: get_cleaned_key_for_path(&path, config), file: None, - language: get_language_for_path(&path, config), + extra_data: get_extra_data_for_path(&path, config), value: translation_value_string, }); @@ -201,6 +202,30 @@ fn get_cleaned_key_for_path(path: &String, config: &ExtensionConfig) -> Option HashMap { + let mut extra_data = HashMap::::new(); + + if let Some(key_details_regex) = config.key.details.as_ref() { + if let Some(cap) = key_details_regex.captures(&path) { + for capture_group_name in key_details_regex.capture_names().flatten() { + let capture_group_result = cap.name(capture_group_name); + + if capture_group_result.is_some() { + extra_data.insert( + capture_group_name.to_string(), + capture_group_result.unwrap().as_str().to_string(), + ); + } + } + } + }; + + extra_data +} + fn get_language_for_path(path: &String, config: &ExtensionConfig) -> Option { config.key.details.as_ref().and_then(|key_details_regex| { key_details_regex.captures(path).and_then(|cap| {