@@ -50,6 +50,22 @@ pub const Builder = struct {
5050 }
5151 }
5252
53+ pub fn addCodeAction (
54+ builder : * Builder ,
55+ kind : UserActionKind ,
56+ params : types.CodeActionParams ,
57+ actions : * std .ArrayListUnmanaged (types.CodeAction ),
58+ ) error {OutOfMemory }! void {
59+ const loc = offsets .rangeToLoc (builder .handle .tree .source , params .range , builder .offset_encoding );
60+
61+ switch (kind ) {
62+ .str_kind_conv = > | conv_kind | switch (conv_kind ) {
63+ .@"string literal to multiline string" = > try handleStringLiteralToMultiline (builder , actions , loc ),
64+ .@"multiline string to string literal" = > try handleMultilineStringToLiteral (builder , actions , loc ),
65+ },
66+ }
67+ }
68+
5369 pub fn createTextEditLoc (self : * Builder , loc : offsets.Loc , new_text : []const u8 ) types.TextEdit {
5470 const range = offsets .locToRange (self .handle .tree .source , loc , self .offset_encoding );
5571 return types.TextEdit { .range = range , .newText = new_text };
@@ -366,6 +382,120 @@ fn handleVariableNeverMutated(builder: *Builder, actions: *std.ArrayListUnmanage
366382 });
367383}
368384
385+ fn handleStringLiteralToMultiline (builder : * Builder , actions : * std .ArrayListUnmanaged (types.CodeAction ), loc : offsets .Loc ) ! void {
386+ const tokens = builder .handle .tree .tokens ;
387+
388+ const str_tok_idx = offsets .sourceIndexToTokenIndex (builder .handle .tree , loc .start );
389+ if (tokens .items (.tag )[str_tok_idx ] != .string_literal ) return ;
390+ const token_src = builder .handle .tree .tokenSlice (str_tok_idx );
391+ const str_conts = token_src [1 .. token_src .len - 1 ]; // Omit leading and trailing '"'
392+ const edit_loc_start = builder .handle .tree .tokenLocation (tokens .items (.start )[str_tok_idx ], str_tok_idx ).line_start ;
393+
394+ var multiline = std .ArrayList (u8 ).init (builder .arena );
395+ const writer = multiline .writer ();
396+
397+ if (builder .handle .tree .tokensOnSameLine (str_tok_idx - | 1 , str_tok_idx )) {
398+ try writer .writeByte ('\n ' );
399+ }
400+
401+ var iter = std .mem .splitSequence (u8 , str_conts , "\\ n" );
402+ while (iter .next ()) | line | {
403+ try writer .print ("\\\\ {s}\n " , .{line });
404+ }
405+
406+ // remove trailing newline in cases where it's not needed
407+ if (str_tok_idx + 1 < tokens .len and ! builder .handle .tree .tokensOnSameLine (str_tok_idx , str_tok_idx + 1 )) {
408+ _ = multiline .pop ();
409+ }
410+
411+ try actions .append (builder .arena , .{
412+ .title = "string literal to multiline string" ,
413+ .kind = .@"refactor.rewrite" ,
414+ .isPreferred = false ,
415+ .edit = try builder .createWorkspaceEdit (&.{
416+ builder .createTextEditLoc (
417+ .{
418+ .start = edit_loc_start ,
419+ .end = edit_loc_start + token_src .len ,
420+ },
421+ multiline .items ,
422+ ),
423+ }),
424+ });
425+ }
426+
427+ fn handleMultilineStringToLiteral (builder : * Builder , actions : * std .ArrayListUnmanaged (types.CodeAction ), loc : offsets .Loc ) ! void {
428+ const token_tags = builder .handle .tree .tokens .items (.tag );
429+ const token_starts = builder .handle .tree .tokens .items (.start );
430+
431+ var multiline_tok_idx = offsets .sourceIndexToTokenIndex (builder .handle .tree , loc .start );
432+ if (token_tags [multiline_tok_idx ] != .multiline_string_literal_line ) return ;
433+
434+ // walk up to the first multiline string literal
435+ const start_tok_idx = blk : {
436+ while (multiline_tok_idx > 0 ) : (multiline_tok_idx -= 1 ) {
437+ if (token_tags [multiline_tok_idx ] != .multiline_string_literal_line ) {
438+ break :blk multiline_tok_idx + 1 ;
439+ }
440+ }
441+ break :blk multiline_tok_idx ;
442+ };
443+
444+ var str_literal = std .ArrayList (u8 ).init (builder .arena );
445+ const writer = str_literal .writer ();
446+
447+ // place string literal on same line as the left adjacent equals sign, if it's there
448+ const prev_tok_idx = start_tok_idx - | 1 ;
449+ const edit_loc_start = blk : {
450+ if (token_tags [prev_tok_idx ] == .equal and ! builder .handle .tree .tokensOnSameLine (prev_tok_idx , start_tok_idx )) {
451+ try writer .writeAll (" \" " );
452+ break :blk builder .handle .tree .tokenLocation (token_starts [prev_tok_idx ], prev_tok_idx ).line_end ;
453+ } else {
454+ try writer .writeByte ('\" ' );
455+ break :blk builder .handle .tree .tokenLocation (token_starts [start_tok_idx ], start_tok_idx ).line_start ;
456+ }
457+ };
458+
459+ // construct string literal out of multiline string literals
460+ var curr_tok_idx = start_tok_idx ;
461+ var edit_loc_end : usize = undefined ;
462+ while (curr_tok_idx < token_tags .len and token_tags [curr_tok_idx ] == .multiline_string_literal_line ) : (curr_tok_idx += 1 ) {
463+ if (curr_tok_idx > start_tok_idx ) {
464+ try writer .writeAll ("\\ n" );
465+ }
466+ const line = builder .handle .tree .tokenSlice (curr_tok_idx );
467+ const end = if (line [line .len - 1 ] == '\n ' )
468+ line .len - 1
469+ else
470+ line .len ;
471+ try writer .writeAll (line [2.. end ]); // Omit the leading '\\', trailing '\n' (if it's there)
472+ edit_loc_end = builder .handle .tree .tokenLocation (token_starts [curr_tok_idx ], curr_tok_idx ).line_end ;
473+ }
474+
475+ try writer .writeByte ('\" ' );
476+
477+ // bring up the semicolon from the next line, if it's there
478+ if (curr_tok_idx < token_tags .len and token_tags [curr_tok_idx ] == .semicolon ) {
479+ try writer .writeByte (';' );
480+ edit_loc_end = builder .handle .tree .tokenLocation (token_starts [curr_tok_idx ], curr_tok_idx ).line_start + 1 ;
481+ }
482+
483+ try actions .append (builder .arena , .{
484+ .title = "multiline string to string literal" ,
485+ .kind = .@"refactor.rewrite" ,
486+ .isPreferred = false ,
487+ .edit = try builder .createWorkspaceEdit (&.{
488+ builder .createTextEditLoc (
489+ .{
490+ .start = edit_loc_start ,
491+ .end = edit_loc_end ,
492+ },
493+ str_literal .items ,
494+ ),
495+ }),
496+ });
497+ }
498+
369499fn detectIndentation (source : []const u8 ) []const u8 {
370500 // Essentially I'm looking for the first indentation in the file.
371501 var i : usize = 0 ;
@@ -575,6 +705,15 @@ const DiagnosticKind = union(enum) {
575705 }
576706};
577707
708+ const UserActionKind = union (enum ) {
709+ str_kind_conv : StrCat ,
710+
711+ const StrCat = enum {
712+ @"string literal to multiline string" ,
713+ @"multiline string to string literal" ,
714+ };
715+ };
716+
578717/// takes the location of an identifier which is part of a discard `_ = location_here;`
579718/// and returns the location from '_' until ';' or null on failure
580719fn getDiscardLoc (text : []const u8 , loc : offsets.Loc ) ? offsets.Loc {
0 commit comments