From 6b3952d2c3d785bc3557f26a7589928c2b7e8487 Mon Sep 17 00:00:00 2001 From: MartinMUU <131437974+MartinMUU@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:33:40 +0100 Subject: [PATCH] Issue#7 numeric (#131) * adding drop possibility * Add functionality drop type * adding part of string as attribute * adding string attr * added grouped_parts * added Quickfixes to add Type as attribute * quickfix cardinality and attribute in one string * fix Quickfix text * quickfix endless while and error text * added documentation * drop constraint feature * fixed drop function * Adding default value for types * adding sum() conversion * adding enroll sum() feature * fixing spelling and doku * adding conversion for avg function --------- Co-authored-by: Thilo Brugger Co-authored-by: Chico Sundermann --- uvls/Cargo.toml | 14 +- uvls/src/core/ast/transform.rs | 46 +++- uvls/src/core/check.rs | 2 + uvls/src/core/util.rs | 23 ++ uvls/src/ide/actions.rs | 447 ++++++++++++++++++++++++++++++++- uvls/src/main.rs | 13 +- 6 files changed, 521 insertions(+), 24 deletions(-) diff --git a/uvls/Cargo.toml b/uvls/Cargo.toml index 746eecf1..bd529414 100644 --- a/uvls/Cargo.toml +++ b/uvls/Cargo.toml @@ -17,7 +17,11 @@ dashmap = "5.4.0" tower-lsp = "0.19.0" tokio = { version = "1", features = ["full"] } ropey = "1.5.0" -flexi_logger = { version = "0.24", features = ["async", "specfile", "compress"] } +flexi_logger = { version = "0.24", features = [ + "async", + "specfile", + "compress", +] } log = "0.4" lazy_static = "1.4.0" parking_lot = "0.12.1" @@ -29,17 +33,17 @@ stacker = "0.1.15" strsim = "0.10.0" min-max-heap = "1.3.0" compact_str = "0.6.1" -ustr = {version = "0.9.0",features = ["serialization"]} +ustr = { version = "0.9.0", features = ["serialization"] } pathfinding = "3.0.14" -log-panics = {version = "2.1.0", features=["with-backtrace"]} +log-panics = { version = "2.1.0", features = ["with-backtrace"] } tokio-util = "0.7.4" serde_json = "1.0" walkdir = "2" +hashbrown = "0.14.3" tree-sitter-uvl = {path="../tree-sitter-uvl"} tree-sitter-json = {git="https://github.com/tree-sitter/tree-sitter-json", rev = "c5a9a9af069de3fe0ef5fa2fa001b6a2e44d75b2"} -hashbrown = "0.13.2" regex = "1" -axum = {version = "0.6.6", features = ["ws"]} +axum = { version = "0.6.6", features = ["ws"] } dioxus = { version = "*" } dioxus-liveview = { version = "*", features = ["axum"] } serde = "1.0.152" diff --git a/uvls/src/core/ast/transform.rs b/uvls/src/core/ast/transform.rs index 0a9d4ac2..31abd63c 100644 --- a/uvls/src/core/ast/transform.rs +++ b/uvls/src/core/ast/transform.rs @@ -597,7 +597,7 @@ fn opt_function_args(state: &mut VisitorState) -> Option> { }) } -fn check_langlvls(state: &mut VisitorState, searched_lang_lvl: LanguageLevel) { +fn check_langlvls(state: &mut VisitorState, searched_lang_lvl: LanguageLevel, is_constraint: bool) { if state.ast.includes.is_empty() { // no includes means, that implicitly everything is included return (); @@ -690,14 +690,25 @@ fn check_langlvls(state: &mut VisitorState, searched_lang_lvl: LanguageLevel) { LanguageLevelType::Any, ), } { - state.push_error_with_type( - 10, - format!( - "Operation does not correspond includes. Please include {:?}", - searched_lang_lvl - ), - ErrorType::WrongLanguageLevel, - ) + if is_constraint { + state.push_error_with_type( + 10, + format!( + "Operation does not correspond includes. Please include {:?} or convert to the included language level.", + searched_lang_lvl + ), + ErrorType::WrongLanguageLevelConstraint, + ) + } else { + state.push_error_with_type( + 10, + format!( + "Operation does not correspond includes. Please include {:?} or convert to the included language level.", + searched_lang_lvl + ), + ErrorType::WrongLanguageLevel, + ) + } } } @@ -789,7 +800,7 @@ fn opt_numeric(state: &mut VisitorState) -> Option { state.goto_field("lhs"); let lhs = opt_numeric(state)?; state.goto_field("op"); - check_langlvls(state, LanguageLevel::Arithmetic(vec![])); + check_langlvls(state, LanguageLevel::Arithmetic(vec![]), false); state.goto_field("rhs"); let rhs = opt_numeric(state)?; Some(Expr::Binary { @@ -813,6 +824,7 @@ fn opt_numeric(state: &mut VisitorState) -> Option { check_langlvls( state, LanguageLevel::Arithmetic(vec![LanguageLevelArithmetic::Aggregate]), + true, ); opt_aggregate(state) } @@ -820,6 +832,7 @@ fn opt_numeric(state: &mut VisitorState) -> Option { check_langlvls( state, LanguageLevel::Type(vec![LanguageLevelType::StringConstraints]), + true, ); if state.child_by_name("tail").is_some() { state.push_error(10, "tailing comma not allowed"); @@ -843,6 +856,7 @@ fn opt_numeric(state: &mut VisitorState) -> Option { check_langlvls( state, LanguageLevel::Type(vec![LanguageLevelType::NumericConstraints]), + true, ); opt_integer(state) } @@ -878,6 +892,8 @@ fn opt_equation(node: Node) -> Option { } fn opt_constraint(state: &mut VisitorState) -> Option { + info!("first ---------sind in op-constraint"); + let span = state.node().byte_range(); state.goto_named(); match state.kind() { @@ -904,7 +920,8 @@ fn opt_constraint(state: &mut VisitorState) -> Option { state.goto_field("lhs"); let lhs = opt_constraint(state)?; state.goto_field("op"); - check_langlvls(state, LanguageLevel::Boolean(vec![])); + info!("sind in op-constraint logic"); + check_langlvls(state, LanguageLevel::Boolean(vec![]), true); state.goto_field("rhs"); let rhs = opt_constraint(state)?; Some(Constraint::Logic { @@ -916,7 +933,8 @@ fn opt_constraint(state: &mut VisitorState) -> Option { state.goto_field("lhs"); let lhs = opt_numeric(state)?; state.goto_field("op"); - check_langlvls(state, LanguageLevel::Arithmetic(vec![])); + info!("sind in op-constraint equation"); + check_langlvls(state, LanguageLevel::Arithmetic(vec![]), true); state.goto_field("rhs"); let rhs = opt_numeric(state)?; Some(Constraint::Equation { @@ -1080,6 +1098,7 @@ fn visit_feature( check_langlvls( state, LanguageLevel::Arithmetic(vec![LanguageLevelArithmetic::FeatureCardinality]), + false, ); opt_cardinality(n, state) }) @@ -1191,7 +1210,7 @@ fn visit_blk_decl(state: &mut VisitorState, parent: Symbol, duplicate: &bool) { visit_feature(state, parent, name, Type::Bool, *duplicate); } "typed_feature" => { - check_langlvls(state, LanguageLevel::Type(vec![])); + check_langlvls(state, LanguageLevel::Type(vec![]), false); let (name, ty) = visit_children(state, |state| { state.goto_field("type"); let ty = match &*state.slice_raw(state.node().byte_range()) { @@ -1235,6 +1254,7 @@ fn visit_blk_decl(state: &mut VisitorState, parent: Symbol, duplicate: &bool) { check_langlvls( state, LanguageLevel::Boolean(vec![LanguageLevelBoolean::GroupCardinality]), + false, ); let card = opt_cardinality(state.node(), state).unwrap_or(Cardinality::Fixed); visit_group(state, parent, GroupMode::Cardinality(card), duplicate); diff --git a/uvls/src/core/check.rs b/uvls/src/core/check.rs index 75cb5201..9c45d299 100644 --- a/uvls/src/core/check.rs +++ b/uvls/src/core/check.rs @@ -20,11 +20,13 @@ pub enum ErrorType { AddIndentation, StartsWithNumber, WrongLanguageLevel, + WrongLanguageLevelConstraint, } impl ErrorType { pub fn from_u32(value: u32) -> ErrorType { match value { + 6 => ErrorType::WrongLanguageLevelConstraint, 5 => ErrorType::WrongLanguageLevel, 4 => ErrorType::StartsWithNumber, 3 => ErrorType::AddIndentation, diff --git a/uvls/src/core/util.rs b/uvls/src/core/util.rs index fa80c464..f5305a88 100644 --- a/uvls/src/core/util.rs +++ b/uvls/src/core/util.rs @@ -83,6 +83,29 @@ pub fn byte_offset(pos: &Position, source: &Rope) -> usize { source.char_to_byte(char_offset(pos, source)) } +pub fn byte_to_line_col(byte: usize, source: &Rope) -> Position { + let mut line = 0; + let mut col = 0; + + for (index, ch) in source.chars().enumerate() { + if index == byte { + break; + } + + if ch == '\n' { + line += 1; + col = 0; + } else { + col += 1; + } + } + + Position { + line, + character: col, + } +} + pub fn containing_blk(mut node: Node) -> Option { node = node.parent()?; while node.kind() != "blk" { diff --git a/uvls/src/ide/actions.rs b/uvls/src/ide/actions.rs index 9e0409f3..7ae6075f 100644 --- a/uvls/src/ide/actions.rs +++ b/uvls/src/ide/actions.rs @@ -6,6 +6,7 @@ use regex::Regex; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; +/// Adds the quickfixes if the feature name contains a dash pub fn rename_dash( params: CodeActionParams, diagnostic: Diagnostic, @@ -17,6 +18,7 @@ pub fn rename_dash( let name = source.slice(start_byte..end_byte).as_str().unwrap(); let new_name = name.replace("-", "_").to_string(); + // Replaces dash with an underscore let code_action_replace = CodeAction { title: format!("Rename to: {}", new_name), kind: Some(CodeActionKind::QUICKFIX), @@ -35,6 +37,8 @@ pub fn rename_dash( diagnostics: Some(vec![diagnostic.clone()]), ..Default::default() }; + + // Puts the complete feature name in quotes let code_action_quotes = CodeAction { title: format!("Rename to: {}", format!("\"{}\"", name)), kind: Some(CodeActionKind::QUICKFIX), @@ -103,6 +107,7 @@ pub fn reference_to_string( } } +/// Adds indentation if the feature is at the same indentation level as keywords pub fn add_indentation( params: CodeActionParams, diagnostic: Diagnostic, @@ -140,6 +145,7 @@ pub fn add_indentation( } } +/// Adds the quickfix to write the full name in double quotes if the feature starts with a number pub fn surround_with_double_quotes( params: CodeActionParams, diagnostic: Diagnostic, @@ -182,6 +188,7 @@ pub fn surround_with_double_quotes( } } +/// Adds the quickfixes if the feature name starts with a number pub fn starts_with_number( params: CodeActionParams, diagnostic: Diagnostic, @@ -205,6 +212,7 @@ pub fn starts_with_number( }; let new_name = format!("{}_{}", &name[number.len()..], number); + // The number at the beginning of the feature name is appended to the end let code_action_number_to_back = CodeAction { title: format!("move number to the back: {}", new_name), kind: Some(CodeActionKind::QUICKFIX), @@ -225,6 +233,8 @@ pub fn starts_with_number( }; let mut result = vec![CodeActionOrCommand::CodeAction(code_action_number_to_back)]; + + // The entire feature is placed in double_quotes match surround_with_double_quotes(params, diagnostic, snapshot) { Ok(Some(v)) => result.append(v.to_owned().as_mut()), _ => (), @@ -235,12 +245,57 @@ pub fn starts_with_number( } } +/// Checks all possible types of quickfixes for the wrong/missing language_level +pub fn wrong_language_level( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + let mut result: Vec = vec![]; + match add_language_level(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + match drop_feature(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + match add_type_as_attribute(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + + return Ok(Some(result)); +} + +pub fn wrong_language_level_constraint( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + let mut result: Vec = vec![]; + match add_language_level(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + match drop_constraint(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + match convert_sum_constraint(params.clone(), diagnostic.clone(), snapshot.clone()) { + Ok(Some(v)) => result.append(v.to_owned().as_mut()), + _ => (), + } + return Ok(Some(result)); +} + +/// Adds the quickfix to include the missing language_level pub fn add_language_level( params: CodeActionParams, diagnostic: Diagnostic, snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, ) -> Result> { - if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot { + if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot.clone() { let reg_sub_lvl = Regex::new(r"\[.*\]").unwrap(); // get position of include let reg_sub = Regex::new(r"^[^\(]*").unwrap(); // get position of include let reg_indent = Regex::new(r"^[^(n\\)]*").unwrap(); // get position of include @@ -284,7 +339,11 @@ pub fn add_language_level( }; // lvl as stated by the error message - let lvl = diagnostic.message.split(" ").last().unwrap(); + let lvl = diagnostic + .message + .split_whitespace() + .nth(7) + .unwrap_or_default(); // retrieve right include from error message let new_include = match reg_sub.find(lvl) { @@ -330,8 +389,390 @@ pub fn add_language_level( diagnostics: Some(vec![diagnostic.clone()]), ..Default::default() }; + + let result = vec![CodeActionOrCommand::CodeAction( + code_action_add_include.clone(), + )]; + + return Ok(Some(result)); + } else { + return Ok(None); + } +} +/// Adds the quickfix to completely delete the corresponding constraint +pub fn drop_constraint( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot { + let start_byte = byte_offset(&diagnostic.range.start, &source); + let mut end_byte = byte_offset(&diagnostic.range.end, &source); + + while end_byte < source.len_chars() + && source.slice(end_byte - 1..end_byte).as_str().unwrap() != "\r" + { + end_byte += 1; + } + + let new_range: Range = Range { + start: (diagnostic.range.start), + end: (byte_to_line_col(end_byte, &source)), + }; + + let name = source + .slice(start_byte..end_byte) + .as_str() + .unwrap() + .replace("\n", "") + .replace("\r", ""); + let code_action_drop = CodeAction { + title: format!("drop constraint: {:?}", name), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(HashMap::>::from([( + params.text_document.uri.clone(), + vec![TextEdit { + range: new_range, + new_text: "".to_string(), + }], + )])), + document_changes: None, + change_annotations: None, + }), + is_preferred: Some(true), + diagnostics: Some(vec![diagnostic.clone()]), + ..Default::default() + }; + return Ok(Some(vec![CodeActionOrCommand::CodeAction( + code_action_drop, + )])); + } else { + return Ok(None); + } +} +/// Adds the quickfix to roll out sum() functions +pub fn convert_sum_constraint( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot { + let start_byte = byte_offset(&diagnostic.range.start, &source); + let end_byte = byte_offset(&diagnostic.range.end, &source); + let constraint = source + .slice(start_byte..end_byte) + .as_str() + .unwrap() + .replace("\n", "") + .replace("\r", "") + .to_string(); + //quickfix is only neccessary if it is a sum() or avg() function + if constraint.contains("sum(") || constraint.contains("avg(") { + // find the attribute name + let constraint_parts: Vec<&str> = constraint.split_whitespace().collect(); + let start_bytes = constraint_parts[0].find("(").unwrap_or(0) + 1; + let end_bytes = constraint_parts[0] + .find(")") + .unwrap_or(constraint_parts[0].len()); + let attribute = &constraint_parts[0][start_bytes..end_bytes]; + + // find all occurances of the attribute + let mut res = String::new(); + let mut attribute_range_start_byte = byte_offset(&Position::new(0, 0), &source); + let mut attribute_range_end_byte = attribute_range_start_byte; + let mut is_not_constraints = true; + let types = ["Integer", "String", "Real", "Boolean"]; + let mut attribute_counter = 0; + + //check for attribute line by line + while attribute_range_end_byte < source.len_chars() && is_not_constraints { + attribute_range_end_byte += 1; + attribute_range_start_byte = attribute_range_end_byte; + while source + .slice(attribute_range_end_byte - 1..attribute_range_end_byte) + .as_str() + .unwrap() + != "\r" + && attribute_range_end_byte < source.len_chars() + { + attribute_range_end_byte += 1; + } + + let source_string = source + .slice(attribute_range_start_byte..attribute_range_end_byte) + .as_str() + .unwrap(); + let source_parts: Vec<&str> = source_string.split_whitespace().collect(); + //stop checking for attribute if the constraint section is reached + for element in source_parts.iter() { + if element.contains("constraints") && source_parts.len() == 1 { + let is_constraint_key = source + .slice(attribute_range_start_byte..attribute_range_start_byte + 11) + .as_str() + .unwrap(); + if is_constraint_key == "constraints" { + is_not_constraints = false; + break; + } + } + // append feature to sum if it contains the attribute + if element.contains(&attribute) { + if res.is_empty() { + //if feature has type the feature name is in index 1 + if types.contains(source_parts.get(0).unwrap()) { + res.push_str(source_parts.get(1).unwrap()); + } else { + res.push_str(source_parts.get(0).unwrap()); + } + res.push_str("."); + res.push_str(attribute); + attribute_counter += 1; + } else { + res.push_str(" + "); + if types.contains(source_parts.get(0).unwrap()) { + res.push_str(source_parts.get(1).unwrap()); + } else { + res.push_str(source_parts.get(0).unwrap()); + } + res.push_str("."); + res.push_str(attribute); + attribute_counter += 1; + } + } + } + } + res = format!("({})", res); + let mut cons = format!("sum({})", attribute); + let mut result = constraint.replacen(&cons, res.as_str(), 1); + let mut title = format!("Roll out sum() function"); + if constraint.contains("avg(") { + cons = format!("avg({})", attribute); + res = format!("({} / {})", res, attribute_counter); + result = constraint.replacen(&cons, res.as_str(), 1); + title = format!("Roll out avg() function"); + } + + let code_action_add_type_as_attribute = CodeAction { + title: title, + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(HashMap::>::from([( + params.text_document.uri.clone(), + vec![TextEdit { + range: diagnostic.range, + new_text: result, + }], + )])), + document_changes: None, + change_annotations: None, + }), + is_preferred: Some(true), + diagnostics: Some(vec![diagnostic.clone()]), + ..Default::default() + }; + return Ok(Some(vec![CodeActionOrCommand::CodeAction( + code_action_add_type_as_attribute, + )])); + } else { + return Ok(None); + } + } else { + return Ok(None); + } +} + +/// Adds the quickfix to completely delete the corresponding feature +pub fn drop_feature( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot { + let start_byte = byte_offset(&diagnostic.range.start, &source); + let end_byte = byte_offset(&diagnostic.range.end, &source); + + let name = source + .slice(start_byte..end_byte) + .as_str() + .unwrap() + .replace("\n", "") + .replace("\r", ""); + let parts: Vec<&str> = name.split_whitespace().collect(); + let code_action_drop = CodeAction { + title: format!("drop {:?}", parts.first().unwrap().to_string()), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(HashMap::>::from([( + params.text_document.uri.clone(), + vec![TextEdit { + range: diagnostic.range, + new_text: parts.last().unwrap().to_string(), + }], + )])), + document_changes: None, + change_annotations: None, + }), + is_preferred: Some(true), + diagnostics: Some(vec![diagnostic.clone()]), + ..Default::default() + }; + return Ok(Some(vec![CodeActionOrCommand::CodeAction( + code_action_drop, + )])); + } else { + return Ok(None); + } +} + +/// Adds the quickfix to append the feature's corresponding Type as an attribute +pub fn add_type_as_attribute( + params: CodeActionParams, + diagnostic: Diagnostic, + snapshot: std::result::Result)>, tower_lsp::jsonrpc::Error>, +) -> Result> { + if let Ok(Some((Draft::UVL { source, .. }, ..))) = snapshot { + let start_byte = byte_offset(&diagnostic.range.start, &source); + let mut end_byte = byte_offset(&diagnostic.range.end, &source); + + while end_byte < source.len_chars() + && source.slice(end_byte - 1..end_byte).as_str().unwrap() != "\r" + { + end_byte += 1; + } + + let new_range: Range = Range { + start: (diagnostic.range.start), + end: (byte_to_line_col(end_byte, &source)), + }; + + let mut name = source + .slice(start_byte..end_byte) + .as_str() + .unwrap() + .replace("\n", "") + .replace("\r", ""); + + //split cardinality string and attribute string + if name.contains("]{") { + name = name.replace("]{", "] {"); + } + + let parts: Vec<&str> = name.split_whitespace().collect(); + + //create one part for cardinality + let mut has_cardinality = false; + let mut cardinality_string: String = String::new(); + + for part in parts.iter().skip(2) { + if part.contains("cardinality") { + has_cardinality = true; + } + + if has_cardinality { + cardinality_string.push_str(part); + if cardinality_string.contains("]") { + break; + } + cardinality_string.push(' ') + } + } + let spaced_cardinality_string: String = format!(" {}", cardinality_string); + + //create one part for attributes + let mut has_attributes = false; + let mut attributes_string: String = String::new(); + + for part in parts.iter().skip(2) { + if part.contains("{") { + has_attributes = true; + } + + if has_attributes { + attributes_string.push_str(part); + if attributes_string.contains("}") { + break; + } + attributes_string.push(' ') + } + } + + //create grouped parts + // 0 = Current Type + // 1 = Feature name + // 2 = cardinality + // 3 = attributes + // 4 = default value of current type + let mut grouped_parts: Vec<&str> = Vec::new(); + grouped_parts.push(parts.get(0).unwrap()); + grouped_parts.push(parts.get(1).unwrap()); + grouped_parts.push(if cardinality_string.is_empty() { + &cardinality_string + } else { + &spaced_cardinality_string + }); + grouped_parts.push(&attributes_string); + match grouped_parts.get(0).unwrap() { + &"Boolean" => grouped_parts.insert(4, " true"), + &"String" => grouped_parts.insert(4, " ''"), + &"Integer" => grouped_parts.insert(4, " 0"), + &"Real" => grouped_parts.insert(4, " 0.0"), + _ => info!("unknown type"), + }; + + //the result replaces the current line. here for no attributes + let mut result: String = format!( + "{}{} {{{}{}}}", + grouped_parts.get(1).unwrap(), + grouped_parts.get(2).unwrap(), + grouped_parts.get(0).unwrap(), + grouped_parts.get(4).unwrap() + ) + .to_string(); + + //here for attributes to append the current Type + if !grouped_parts.get(3).unwrap().is_empty() { + let attributes = grouped_parts.get(3).unwrap().to_string(); + let attribute_without_bracket = &attributes[0..attributes.len() - 1]; + let new_attributes = format!( + "{}, {}{}}}", + attribute_without_bracket, + grouped_parts.get(0).unwrap(), + grouped_parts.get(4).unwrap() + ); + + result = format!( + "{}{} {}", + grouped_parts.get(1).unwrap(), + grouped_parts.get(2).unwrap(), + new_attributes, + ) + .to_string(); + } + + let code_action_add_type_as_attribute = CodeAction { + title: format!( + "convert {:?} to attribute", + grouped_parts.get(0).unwrap().to_string() + ), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(HashMap::>::from([( + params.text_document.uri.clone(), + vec![TextEdit { + range: new_range, + new_text: result, + }], + )])), + document_changes: None, + change_annotations: None, + }), + is_preferred: Some(true), + diagnostics: Some(vec![diagnostic.clone()]), + ..Default::default() + }; return Ok(Some(vec![CodeActionOrCommand::CodeAction( - code_action_add_include, + code_action_add_type_as_attribute, )])); } else { return Ok(None); diff --git a/uvls/src/main.rs b/uvls/src/main.rs index 161841d3..fd3f4765 100644 --- a/uvls/src/main.rs +++ b/uvls/src/main.rs @@ -732,9 +732,9 @@ impl LanguageServer for Backend { } } + /// Checks if there is a quick fix for the current diagnostic message async fn code_action(&self, params: CodeActionParams) -> Result> { for diagnostic in params.clone().context.diagnostics { - // Checks if there is a quick fix for the current diagnostic message match diagnostic.clone().data { Some(serde_json::value::Value::Number(number)) => { match ErrorType::from_u32(number.as_u64().unwrap_or(0) as u32) { @@ -768,11 +768,18 @@ impl LanguageServer for Backend { ) } ErrorType::WrongLanguageLevel => { - return actions::add_language_level( + return actions::wrong_language_level( params.clone(), diagnostic, self.snapshot(¶ms.text_document.uri, false).await, - ); + ) + } + ErrorType::WrongLanguageLevelConstraint => { + return actions::wrong_language_level_constraint( + params.clone(), + diagnostic, + self.snapshot(¶ms.text_document.uri, false).await, + ) } } }