diff --git a/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs b/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs index 066f15385..e822d8a3b 100644 --- a/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs +++ b/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs @@ -211,6 +211,19 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P | _ -> None else None) + |> Option.orElseWith (fun () -> + if synMatchClauseList.IsEmpty then + match debugPoint with + | DebugPointAtBinding.Yes range -> + { MatchWithOrFunctionRange = range + Expr = matchExpr + Clauses = [] } + |> Some + | _ -> None + else + None + + ) | SynExpr.App(_exprAtomicFlag, _isInfix, synExpr1, synExpr2, _range) -> List.tryPick walkExpr [ synExpr1; synExpr2 ] @@ -496,13 +509,16 @@ let tryFindCaseInsertionParamsAtPos (codeGenService: ICodeGenerationService) pos let! insertionParams = tryFindInsertionParams codeGenService document patMatchExpr return patMatchExpr, insertionParams else - return! None + return + patMatchExpr, + { InsertionPos = patMatchExpr.Expr.Range.End + IndentColumn = patMatchExpr.Expr.Range.Start.Column } } let tryFindUnionDefinitionFromPos (codeGenService: ICodeGenerationService) pos document = asyncOption { - let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document let! _, symbolUse = codeGenService.GetSymbolAndUseAtPositionOfKind(document.FullName, pos, SymbolKind.Ident) + let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document let! superficialTypeDefinition = @@ -510,6 +526,7 @@ let tryFindUnionDefinitionFromPos (codeGenService: ICodeGenerationService) pos d let! symbolUse = symbolUse match symbolUse.Symbol with + | SymbolPatterns.MemberFunctionOrValue(mfv) -> return Some mfv.FullType.TypeDefinition | SymbolPatterns.UnionCase(case) when case.ReturnType.HasTypeDefinition -> return Some case.ReturnType.TypeDefinition | SymbolPatterns.FSharpEntity(entity, _, _) -> return Some entity diff --git a/src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs b/src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs index 27cf2bf5a..3832ed3a8 100644 --- a/src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs +++ b/src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs @@ -18,22 +18,47 @@ let fix (getTextReplacements: unit -> Map) = Run.ifDiagnosticByCode (Set.ofList [ "25" ]) (fun diagnostic codeActionParams -> + let getCasePosFromCaseLine (lines: IFSACSourceText) (fcsRange: FcsRange) = + result { + let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line") + + let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line") + + let! caseCol = + match caseLine.IndexOf('|') with + | -1 -> Error "Invalid case line" + | idx -> Ok(uint32 idx + 3u) // Find column of first case in pattern matching + + let casePos = + { Line = uint32 nextLine.Line - 1u + Character = caseCol } + + return casePos + } + + let getCasePosFromMatch (lines: IFSACSourceText) (fcsRange: FcsRange) = + result { + let! matchLine = lines.GetLine fcsRange.Start |> Result.ofOption (fun _ -> "no current line") + let caseCol = matchLine.IndexOf("match") + + let casePos = + { Line = uint32 fcsRange.Start.Line - 1u + Character = uint32 caseCol + 7u } + + return casePos + } + asyncResult { - let fileName = codeActionParams.TextDocument.GetFilePath() |> Utils.normalizePath + let fileName = codeActionParams.TextDocument.GetFilePath() |> normalizePath let! lines = getFileLines fileName // try to find the first case already written let fcsRange = protocolRangeToRange (FSharp.UMX.UMX.untag fileName) diagnostic.Range - let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line") - - let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line") - - let caseCol = uint32 (caseLine.IndexOf('|')) + 3u // Find column of first case in pattern matching - let casePos = - { Line = uint32 nextLine.Line - 1u - Character = caseCol } + let! casePos = + (getCasePosFromCaseLine lines fcsRange) + |> Result.orElseWith (fun _ -> getCasePosFromMatch lines fcsRange) let casePosFCS = protocolPosToPos casePos diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index 4095b0203..bbfaf92f2 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -2100,7 +2100,7 @@ let private generateUnionCasesTests state = serverTestList (nameof GenerateUnionCases) state config None (fun server -> [ let _selectCodeFix = CodeFix.withTitle GenerateUnionCases.title - testCaseAsync "can generate match cases for a simple DU" + testCaseAsync "can generate match cases for a simple DU with one case" <| CodeFix.check server """ @@ -2122,6 +2122,29 @@ let private generateUnionCasesTests state = | A -> () | B -> failwith "---" | C -> failwith "---" + """ + + testCaseAsync "can generate match cases for a simple DU without cases" + <| CodeFix.check + server + """ + type Letter = A | B | C + + let char = A + + match $0char with + """ + (Diagnostics.expectCode "25") + (CodeFix.withTitle GenerateUnionCases.title) + """ + type Letter = A | B | C + + let char = A + + match char with + | A -> failwith "---" + | B -> failwith "---" + | C -> failwith "---" """ ]) let private makeDeclarationMutableTests state = @@ -2724,6 +2747,7 @@ let private replaceWithSuggestionTests state = let validateDiags (diags: Diagnostic[]) = Diagnostics.expectCode "39" diags let messages = diags |> Array.map (fun d -> d.Message) |> String.concat "\n" + Expect.exists diags (fun (d: Diagnostic) -> d.Message.Contains "Maybe you want one of the following:") @@ -3175,8 +3199,8 @@ let private removePatternArgumentTests state = | B = 2 do - let (E.A x$0) = E.A - () + let (E.A x$0) = E.A + () """ (Diagnostics.expectCode "3191") selectCodeFix @@ -3186,8 +3210,8 @@ let private removePatternArgumentTests state = | B = 2 do - let (E.A) = E.A - () + let (E.A) = E.A + () """ testCaseAsync "Local literal constant pattern qualified parameter"