Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

implemented code action for converting between doc comments and regular comments

+574 -7
+43
CHANGELOG.md
··· 347 347 348 348 ([Gavin Morrow](https://github.com/gavinmorrow)) 349 349 350 + - The language server now has "Convert to documentation comment" and 351 + "Convert to regular comment" code actions. For example: 352 + 353 + ```gleam 354 + // Module description. 355 + // Code action available here. 356 + 357 + // Comment before function. 358 + // Another code action here. 359 + pub fn wibble() { 360 + // No code action here. 361 + todo 362 + } 363 + 364 + /// Doc comment. 365 + /// Another code action here. 366 + pub fn wobble () { 367 + todo 368 + } 369 + ``` 370 + 371 + Triggering the code actions in all of these places will result in: 372 + 373 + ```gleam 374 + //// Module description. 375 + //// Code action available here. 376 + 377 + /// Comment before function. 378 + /// Another code action here. 379 + pub fn wibble() { 380 + // No code action here. 381 + todo 382 + } 383 + 384 + // Doc comment. 385 + // Another code action here. 386 + pub fn wobble () { 387 + todo 388 + } 389 + ``` 390 + 391 + ([Daniel Venable](https://github.com/DanielVenable)) 392 + 350 393 ### Formatter 351 394 352 395 - Performance of the formatter has been improved.
+137
language-server/src/code_action.rs
··· 12962 12962 .push_to(actions); 12963 12963 } 12964 12964 } 12965 + 12966 + /// Code action to switch between doc and regular comments. 12967 + pub struct ConvertBetweenDocAndRegularComment<'a> { 12968 + module: &'a Module, 12969 + lines: &'a LineNumbers, 12970 + params: &'a CodeActionParams, 12971 + } 12972 + 12973 + impl<'a> ConvertBetweenDocAndRegularComment<'a> { 12974 + pub fn new(module: &'a Module, lines: &'a LineNumbers, params: &'a CodeActionParams) -> Self { 12975 + Self { 12976 + module, 12977 + lines, 12978 + params, 12979 + } 12980 + } 12981 + 12982 + pub fn code_actions(self) -> Vec<CodeAction> { 12983 + let line = self.params.range.start.line; 12984 + let num_slashes = self 12985 + .count_leading_slashes(line) 12986 + .expect("Line number should be valid"); 12987 + 12988 + if !(2..=4).contains(&num_slashes) { 12989 + return vec![]; 12990 + } 12991 + 12992 + let start_line = self 12993 + .find_comment_edge((0..line).rev(), num_slashes) 12994 + .unwrap_or(line); 12995 + let end_line = self 12996 + .find_comment_edge(line + 1.., num_slashes) 12997 + .unwrap_or(line); 12998 + 12999 + if self.params.range.end.line > end_line { 13000 + return vec![]; 13001 + } 13002 + 13003 + let comment = match num_slashes { 13004 + 2 if self.is_module_comment(start_line, end_line) => "////", 13005 + 2 if self.can_have_doc_comment(end_line) => "///", 13006 + 3 | 4 => "//", 13007 + _ => return vec![], 13008 + }; 13009 + 13010 + let mut edits = TextEdits::new(self.lines); 13011 + for line in start_line..=end_line { 13012 + let start = next_nonwhitespace( 13013 + &self.module.code, 13014 + self.line_start(line).expect("Line number should be valid"), 13015 + ); 13016 + edits.replace(SrcSpan::new(start, start + num_slashes), comment.to_owned()); 13017 + } 13018 + 13019 + let action_name = if comment == "//" { 13020 + "Convert to regular comment" 13021 + } else { 13022 + "Convert to documentation comment" 13023 + }; 13024 + 13025 + let mut action = Vec::with_capacity(1); 13026 + CodeActionBuilder::new(action_name) 13027 + .kind(CodeActionKind::RefactorRewrite) 13028 + .changes(self.params.text_document.uri.clone(), edits.edits) 13029 + .push_to(&mut action); 13030 + action 13031 + } 13032 + 13033 + fn count_leading_slashes(&self, line: u32) -> Option<u32> { 13034 + let mut count = 0; 13035 + for c in self.module.code[self.line_start(line)? as usize..].chars() { 13036 + if c == '/' { 13037 + count += 1; 13038 + } else if !c.is_whitespace() || c == '\n' || count > 0 { 13039 + break; 13040 + } 13041 + } 13042 + Some(count) 13043 + } 13044 + 13045 + fn line_start(&self, line: u32) -> Option<u32> { 13046 + self.lines.line_starts.get(line as usize).copied() 13047 + } 13048 + 13049 + /// Find the last line in the range that is part of the same comment. 13050 + fn find_comment_edge(&self, range: impl Iterator<Item = u32>, num_slashes: u32) -> Option<u32> { 13051 + range 13052 + .take_while(|&i| { 13053 + self.count_leading_slashes(i) 13054 + .is_some_and(|n| n == num_slashes) 13055 + }) 13056 + .last() 13057 + } 13058 + 13059 + fn is_module_comment(&self, start_line: u32, end_line: u32) -> bool { 13060 + previous_nonwhitespace( 13061 + &self.module.code, 13062 + self.line_start(start_line) 13063 + .expect("Line number should be valid"), 13064 + ) == 0 13065 + && self 13066 + .line_start(end_line + 1) 13067 + .is_none_or(|position| self.module.extra.empty_lines.contains(&position)) 13068 + } 13069 + 13070 + /// Check if the comment is before a node that can have a doc comment, e.g. a function. 13071 + fn can_have_doc_comment(&self, end_line: u32) -> bool { 13072 + self.line_start(end_line + 1).is_some_and(|position| { 13073 + let next_node = next_nonwhitespace(&self.module.code, position); 13074 + let definitions = &self.module.ast.definitions; 13075 + definitions 13076 + .functions 13077 + .iter() 13078 + .any(|function| function.location.contains(next_node)) 13079 + || definitions 13080 + .constants 13081 + .iter() 13082 + .any(|constant| constant.location.contains(next_node)) 13083 + || definitions 13084 + .type_aliases 13085 + .iter() 13086 + .any(|type_alias| type_alias.location.contains(next_node)) 13087 + || definitions.custom_types.iter().any(|custom_type| { 13088 + custom_type.location.contains(next_node) 13089 + || custom_type.constructors.iter().any(|constructor| { 13090 + constructor.location.contains(next_node) 13091 + || constructor.arguments.iter().any(|argument| { 13092 + argument 13093 + .label 13094 + .as_ref() 13095 + .is_some_and(|label| label.0.contains(next_node)) 13096 + }) 13097 + }) 13098 + }) 13099 + }) 13100 + } 13101 + }
+11 -7
language-server/src/engine.rs
··· 51 51 DownloadDependencies, MakeLocker, 52 52 code_action::{ 53 53 AddAnnotations, AddMissingTypeParameter, AddOmittedLabels, AnnotateTopLevelDefinitions, 54 - CodeActionBuilder, CollapseNestedCase, ConvertFromUse, ConvertToFunctionCall, 55 - ConvertToPipe, ConvertToUse, CreateUnknownModule, ExpandFunctionCapture, ExtractConstant, 56 - ExtractFunction, ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, 57 - FixBinaryOperation, FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, 58 - GenerateJsonEncoder, GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, 59 - MergeCaseBranches, PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, 60 - RemoveEchos, RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 54 + CodeActionBuilder, CollapseNestedCase, ConvertBetweenDocAndRegularComment, ConvertFromUse, 55 + ConvertToFunctionCall, ConvertToPipe, ConvertToUse, CreateUnknownModule, 56 + ExpandFunctionCapture, ExtractConstant, ExtractFunction, ExtractVariable, 57 + FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, 58 + FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, 59 + GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, MergeCaseBranches, 60 + PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, RemoveEchos, 61 + RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 61 62 UnwrapAnonymousFunction, UseLabelShorthandSyntax, WrapInAnonymousFunction, WrapInBlock, 62 63 code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 63 64 code_action_convert_unqualified_constructor_to_qualified, code_action_generate_type, ··· 555 556 ); 556 557 actions.extend(DiscardUnusedVariable::new(module, &lines, &params).code_actions()); 557 558 code_action_fix_deprecated_pipe(module, &lines, &params, &mut actions); 559 + actions.extend( 560 + ConvertBetweenDocAndRegularComment::new(module, &lines, &params).code_actions(), 561 + ); 558 562 559 563 actions.sort_by_key(|one| { 560 564 let preferred_key = if one.is_preferred == Some(true) { 0 } else { 1 };
+181
language-server/src/tests/action.rs
··· 208 208 const REMOVE_REDUNDANT_RECORD_UPDATE: &str = "Remove redundant record update"; 209 209 const DISCARD_UNUSED_VARIABLE: &str = "Discard unused variable"; 210 210 const ADD_EXTRA_PARENTHESES: &str = "Add extra parentheses"; 211 + const CONVERT_TO_DOCUMENTATION_COMMENT: &str = "Convert to documentation comment"; 212 + const CONVERT_TO_REGULAR_COMMENT: &str = "Convert to regular comment"; 211 213 212 214 macro_rules! assert_code_action { 213 215 ($title:expr, $code:literal, $range_selector:expr $(,)?) => { ··· 15420 15422 find_position_of("wibble").under_char('i').to_selection() 15421 15423 ); 15422 15424 } 15425 + 15426 + #[test] 15427 + fn convert_to_doc_comment_on_function() { 15428 + assert_code_action!( 15429 + CONVERT_TO_DOCUMENTATION_COMMENT, 15430 + "// wobble 15431 + fn wibble() { 15432 + todo 15433 + }", 15434 + find_position_of("wobble").to_selection() 15435 + ); 15436 + } 15437 + 15438 + #[test] 15439 + fn convert_to_doc_comment_on_type() { 15440 + assert_code_action!( 15441 + CONVERT_TO_DOCUMENTATION_COMMENT, 15442 + "// wobble 15443 + type Wibble { 15444 + Wibble 15445 + }", 15446 + find_position_of("wobble").to_selection() 15447 + ); 15448 + } 15449 + 15450 + #[test] 15451 + fn convert_to_doc_comment_on_constant() { 15452 + assert_code_action!( 15453 + CONVERT_TO_DOCUMENTATION_COMMENT, 15454 + "// wobble 15455 + const wibble: Int = 42", 15456 + find_position_of("wobble").to_selection() 15457 + ); 15458 + } 15459 + 15460 + #[test] 15461 + fn convert_to_doc_comment_on_type_alias() { 15462 + assert_code_action!( 15463 + CONVERT_TO_DOCUMENTATION_COMMENT, 15464 + "// wobble 15465 + type Wibble = Int", 15466 + find_position_of("wobble").to_selection() 15467 + ); 15468 + } 15469 + 15470 + #[test] 15471 + fn convert_to_doc_comment_on_variant_constructor_definition() { 15472 + assert_code_action!( 15473 + CONVERT_TO_DOCUMENTATION_COMMENT, 15474 + "type Wibble { 15475 + // wobble 15476 + Wibble(Int) 15477 + }", 15478 + find_position_of("wobble").to_selection() 15479 + ); 15480 + } 15481 + 15482 + #[test] 15483 + fn convert_to_doc_comment_on_record_label_definition() { 15484 + assert_code_action!( 15485 + CONVERT_TO_DOCUMENTATION_COMMENT, 15486 + "type Wibble { 15487 + Wibble( 15488 + // wobble 15489 + wibble: Int, 15490 + ) 15491 + }", 15492 + find_position_of("wobble").to_selection() 15493 + ); 15494 + } 15495 + 15496 + #[test] 15497 + fn no_convert_to_doc_comment_triggered_on_doc_comment() { 15498 + assert_no_code_actions!( 15499 + CONVERT_TO_DOCUMENTATION_COMMENT, 15500 + "/// wobble 15501 + fn wibble() { 15502 + todo 15503 + }", 15504 + find_position_of("wobble").to_selection() 15505 + ); 15506 + } 15507 + 15508 + #[test] 15509 + fn no_convert_to_doc_comment_inside_function() { 15510 + assert_no_code_actions!( 15511 + CONVERT_TO_DOCUMENTATION_COMMENT, 15512 + "fn wibble() { 15513 + // wobble 15514 + todo 15515 + }", 15516 + find_position_of("wobble").to_selection() 15517 + ); 15518 + } 15519 + 15520 + #[test] 15521 + fn no_convert_to_doc_comment_before_import() { 15522 + assert_no_code_actions!( 15523 + CONVERT_TO_DOCUMENTATION_COMMENT, 15524 + "// wobble 15525 + import wibble", 15526 + find_position_of("wobble").to_selection() 15527 + ); 15528 + } 15529 + 15530 + #[test] 15531 + fn convert_to_module_doc_comment() { 15532 + assert_code_action!( 15533 + CONVERT_TO_DOCUMENTATION_COMMENT, 15534 + "// wobble 15535 + 15536 + import wibble 15537 + ", 15538 + find_position_of("wobble").to_selection() 15539 + ); 15540 + } 15541 + 15542 + #[test] 15543 + fn convert_to_regular_comment() { 15544 + assert_code_action!( 15545 + CONVERT_TO_REGULAR_COMMENT, 15546 + "/// wobble 15547 + fn wibble() { 15548 + todo 15549 + }", 15550 + find_position_of("wobble").to_selection() 15551 + ); 15552 + } 15553 + 15554 + #[test] 15555 + fn convert_module_doc_comment_to_regular_comment() { 15556 + assert_code_action!( 15557 + CONVERT_TO_REGULAR_COMMENT, 15558 + "//// wobble 15559 + 15560 + import wibble 15561 + ", 15562 + find_position_of("wobble").to_selection() 15563 + ); 15564 + } 15565 + 15566 + #[test] 15567 + fn convert_multiline_comment_with_doc_comment() { 15568 + assert_code_action!( 15569 + CONVERT_TO_DOCUMENTATION_COMMENT, 15570 + "// wibble 15571 + // wobble 15572 + // wibble 15573 + fn wibble() { 15574 + todo 15575 + }", 15576 + find_position_of("wobble").to_selection() 15577 + ); 15578 + } 15579 + 15580 + #[test] 15581 + fn convert_to_doc_comment_no_affect_other_comment() { 15582 + assert_code_action!( 15583 + CONVERT_TO_DOCUMENTATION_COMMENT, 15584 + "// wibble 15585 + 15586 + // wobble 15587 + fn wibble() { 15588 + todo 15589 + }", 15590 + find_position_of("wobble").to_selection() 15591 + ); 15592 + } 15593 + 15594 + #[test] 15595 + fn convert_to_regular_comment_no_affect_other_comment() { 15596 + assert_code_action!( 15597 + CONVERT_TO_REGULAR_COMMENT, 15598 + "/// wibble 15599 + 15600 + /// wobble", 15601 + find_position_of("wibble").to_selection() 15602 + ); 15603 + }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_module_doc_comment_to_regular_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "//// wobble\n\nimport wibble\n" 4 + --- 5 + ----- BEFORE ACTION 6 + //// wobble 7 + 8 + 9 + import wibble 10 + 11 + 12 + ----- AFTER ACTION 13 + // wobble 14 + 15 + import wibble
+21
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_multiline_comment_with_doc_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wibble\n// wobble\n// wibble\nfn wibble() {\n todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + // wibble 7 + // wobble 8 + 9 + // wibble 10 + fn wibble() { 11 + todo 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + /// wibble 17 + /// wobble 18 + /// wibble 19 + fn wibble() { 20 + todo 21 + }
+21
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_no_affect_other_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wibble\n\n// wobble\nfn wibble() {\n todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + // wibble 7 + 8 + // wobble 9 + 10 + fn wibble() { 11 + todo 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + // wibble 17 + 18 + /// wobble 19 + fn wibble() { 20 + todo 21 + }
+13
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_constant.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wobble\nconst wibble: Int = 42" 4 + --- 5 + ----- BEFORE ACTION 6 + // wobble 7 + 8 + const wibble: Int = 42 9 + 10 + 11 + ----- AFTER ACTION 12 + /// wobble 13 + const wibble: Int = 42
+17
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wobble\nfn wibble() {\n todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + // wobble 7 + 8 + fn wibble() { 9 + todo 10 + } 11 + 12 + 13 + ----- AFTER ACTION 14 + /// wobble 15 + fn wibble() { 16 + todo 17 + }
+21
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_record_label_definition.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "type Wibble {\n Wibble(\n // wobble\n wibble: Int,\n )\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + type Wibble { 7 + Wibble( 8 + // wobble 9 + 10 + wibble: Int, 11 + ) 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + type Wibble { 17 + Wibble( 18 + /// wobble 19 + wibble: Int, 20 + ) 21 + }
+17
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_type.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wobble\ntype Wibble {\n Wibble\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + // wobble 7 + 8 + type Wibble { 9 + Wibble 10 + } 11 + 12 + 13 + ----- AFTER ACTION 14 + /// wobble 15 + type Wibble { 16 + Wibble 17 + }
+13
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_type_alias.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wobble\ntype Wibble = Int" 4 + --- 5 + ----- BEFORE ACTION 6 + // wobble 7 + 8 + type Wibble = Int 9 + 10 + 11 + ----- AFTER ACTION 12 + /// wobble 13 + type Wibble = Int
+17
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_doc_comment_on_variant_constructor_definition.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "type Wibble {\n // wobble\n Wibble(Int)\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + type Wibble { 7 + // wobble 8 + 9 + Wibble(Int) 10 + } 11 + 12 + 13 + ----- AFTER ACTION 14 + type Wibble { 15 + /// wobble 16 + Wibble(Int) 17 + }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_module_doc_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "// wobble\n\nimport wibble\n" 4 + --- 5 + ----- BEFORE ACTION 6 + // wobble 7 + 8 + 9 + import wibble 10 + 11 + 12 + ----- AFTER ACTION 13 + //// wobble 14 + 15 + import wibble
+17
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_regular_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "/// wobble\nfn wibble() {\n todo\n}" 4 + --- 5 + ----- BEFORE ACTION 6 + /// wobble 7 + 8 + fn wibble() { 9 + todo 10 + } 11 + 12 + 13 + ----- AFTER ACTION 14 + // wobble 15 + fn wibble() { 16 + todo 17 + }
+15
language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_regular_comment_no_affect_other_comment.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "/// wibble\n\n/// wobble" 4 + --- 5 + ----- BEFORE ACTION 6 + /// wibble 7 + 8 + 9 + /// wobble 10 + 11 + 12 + ----- AFTER ACTION 13 + // wibble 14 + 15 + /// wobble