diff --git a/src/fsharp/vs/Symbols.fs b/src/fsharp/vs/Symbols.fs index 7738187739..83e93d7b39 100644 --- a/src/fsharp/vs/Symbols.fs +++ b/src/fsharp/vs/Symbols.fs @@ -872,6 +872,18 @@ and FSharpMemberFunctionOrValue(g:TcGlobals, thisCcu, tcImports, d:FSharpMemberO | Some v -> Some v.Range | None -> base.DeclarationLocation + member x.Overloads = + checkIsResolved() + match d with + | M m -> + match item with + | Item.MethodGroup (name, methodInfos) -> + methodInfos + |> List.filter (fun methodInfo -> not (methodInfo.NumArgs = m.NumArgs) ) + |> List.map (fun mi -> FSharpMemberFunctionOrValue(g, thisCcu, tcImports, M mi, item)) + |> Some + | _ -> None + | _ -> None member x.DeclarationLocation = checkIsResolved() diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index 8dbaedfdab..535d8f1315 100755 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -942,6 +942,71 @@ type TypeCheckInfo let items = if isInterfaceFile then items |> List.filter IsValidSignatureFileItem else items DeclarationSet.Create(infoReader,m,denv,items,reactorOps,checkAlive)) (fun msg -> DeclarationSet.Error msg) + + member x.GetDeclarationSymbols (parseResultsOpt:ParseFileResults option, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, hasTextChangedSinceLastTypecheck) = + let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName + ErrorScope.Protect + Range.range0 + (fun () -> + match GetDeclItemsForNamesAtPosition(parseResultsOpt, Some qualifyingNames, Some partialName, line, lineStr, colAtEndOfNamesAndResidue, ResolveTypeNamesToCtors, ResolveOverloads.Yes, hasTextChangedSinceLastTypecheck) with + | None -> List.Empty + | Some(items,denv,m) -> + let items = items |> filterIntellisenseCompletionsBasedOnParseContext (parseResultsOpt |> Option.bind (fun x -> x.ParseTree)) (mkPos line colAtEndOfNamesAndResidue) + let items = if isInterfaceFile then items |> List.filter IsValidSignatureFileItem else items + + //do filtering like Declarationset + let items = items |> RemoveExplicitlySuppressed g + + // Sort by name. For things with the same name, + // - show types with fewer generic parameters first + // - show types before over other related items - they usually have very useful XmlDocs + let items = + items |> List.sortBy (fun d -> + let n = + match d with + | Item.Types (_,(TType_app(tcref,_) :: _)) -> 1 + tcref.TyparsNoRange.Length + // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name + | Item.FakeInterfaceCtor (TType_app(tcref,_)) + | Item.DelegateCtor (TType_app(tcref,_)) -> 1000 + tcref.TyparsNoRange.Length + // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name + | Item.CtorGroup (_, (cinfo :: _)) -> 1000 + 10 * (tcrefOfAppTy g cinfo.EnclosingType).TyparsNoRange.Length + | _ -> 0 + (d.DisplayName,n)) + + // Remove all duplicates. We've put the types first, so this removes the DelegateCtor and DefaultStructCtor's. + let items = items |> RemoveDuplicateItems g + + if verbose then dprintf "service.ml: mkDecls: %d found groups after filtering\n" (List.length items); + + // Group by display name + let items = items |> List.groupBy (fun d -> d.DisplayName) + + // Filter out operators (and list) + let items = + // Check whether this item looks like an operator. + let isOpItem(nm,item) = + match item with + | [Item.Value _] + | [Item.MethodGroup(_,[_])] -> + (IsOpName nm) && nm.[0]='(' && nm.[nm.Length-1]=')' + | [Item.UnionCase _] -> IsOpName nm + | _ -> false + + let isFSharpList nm = (nm = "[]") // list shows up as a Type and a UnionCase, only such entity with a symbolic name, but want to filter out of intellisense + + items |> List.filter (fun (nm,items) -> not (isOpItem(nm,items)) && not(isFSharpList nm)) + + + let items = + // Filter out duplicate names + items |> List.map (fun (nm,itemsWithSameName) -> + match itemsWithSameName with + | [] -> failwith "Unexpected empty bag" + | items -> items |> List.map (fun item -> FSharpSymbol.Create(g, thisCcu, tcImports, item))) + + //end filtering + items) + (fun msg -> List.empty) member scope.GetReferenceResolutionToolTipText(line,col) : ToolTipText = let pos = mkPos line col @@ -1673,6 +1738,10 @@ type CheckFileResults(errors: ErrorInfo[], scopeOptX: TypeCheckInfo option, buil let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) reactorOp DeclarationSet.Empty (fun scope -> scope.GetDeclarations(parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, hasTextChangedSinceLastTypecheck)) + member info.GetDeclarationSymbols(parseResultsOpt, line, colAtEndOfNamesAndResidue, lineStr, qualifyingNames, partialName, ?hasTextChangedSinceLastTypecheck) = + let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) + reactorOp List.empty (fun scope -> scope.GetDeclarationSymbols(parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, hasTextChangedSinceLastTypecheck)) + /// Resolve the names at the given location to give a data tip member info.GetToolTipTextAlternate(line, colAtEndOfNames, lineStr, names, tokenTag) = let dflt = ToolTipText [] diff --git a/src/fsharp/vs/service.fsi b/src/fsharp/vs/service.fsi index 687fb2ad86..5f1bd7c513 100755 --- a/src/fsharp/vs/service.fsi +++ b/src/fsharp/vs/service.fsi @@ -175,6 +175,30 @@ type CheckFileResults = member GetDeclarationsAlternate : ParsedFileResultsOpt:ParseFileResults option * line: int * colAtEndOfPartialName: int * lineText:string * qualifyingNames: string list * partialName: string * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) -> Async + /// Get the items for a declaration list in FSharpSymbol format + /// + /// + /// If this is present, it is used to filter declarations based on location in the + /// parse tree, specifically at 'open' declarations, 'inherit' of class or interface + /// 'record field' locations and r.h.s. of 'range' operator a..b + /// + /// The line number where the completion is happening + /// The column number (1-based) at the end of the 'names' text + /// The long identifier to the left of the '.' + /// The residue of a partial long identifier to the right of the '.' + /// The residue of a partial long identifier to the right of the '.' + /// + /// The text of the line where the completion is happening. This is only used to make a couple + /// of adhoc corrections to completion accuracy (e.g. checking for "..") + /// + /// + /// If text has been used from a captured name resolution from the typecheck, then + /// callback to the client to check if the text has changed. If it has, then give up + /// and assume that we're going to repeat the operation later on. + /// + member GetDeclarationSymbols : ParsedFileResultsOpt:ParseFileResults option * line: int * colAtEndOfPartialName: int * lineText:string * qualifyingNames: string list * partialName: string * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) -> Async + + /// Compute a formatted tooltip for the given location /// /// The line number where the information is being requested. diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index 1cb522411e..c327548bed 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -262,4 +262,65 @@ type Test() = let decls = typeCheckResults.GetDeclarationsAlternate(Some untyped, 4, 15, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously decls.Items |> Seq.exists (fun d -> d.Name = "abc") |> shouldEqual true + +[] +let ``Symbol based find function from member 1`` () = + let input = + """ +type Test() = + let abc a b c = a + b + c + member x.Test = """ + + // Split the input & define file name + let inputLines = input.Split('\n') + let file = "/home/user/Test.fsx" + let untyped, typeCheckResults = parseAndTypeCheckFileInProject(file, input) + + let decls = typeCheckResults.GetDeclarationSymbols(Some untyped, 4, 21, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + let item = decls |> List.tryFind (fun d -> d.Head.DisplayName = "abc") + match item with + | Some items -> + for symbol in items do + printf "%s" symbol.DisplayName + | _ -> () + decls |> Seq.exists (fun d -> d.Head.DisplayName = "abc") |> shouldEqual true + +[] +let ``Symbol based find function from member 2`` () = + let input = + """ +type Test() = + let abc a b c = a + b + c + member x.Test = a""" + + // Split the input & define file name + let inputLines = input.Split('\n') + let file = "/home/user/Test.fsx" + let untyped, typeCheckResults = parseAndTypeCheckFileInProject(file, input) + + let decls = typeCheckResults.GetDeclarationSymbols(Some untyped, 4, 22, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + let item = decls |> List.tryFind (fun d -> d.Head.DisplayName = "abc") + match item with + | Some items -> + for symbol in items do + printf "%s" symbol.DisplayName + | _ -> () + decls |> Seq.exists (fun d -> d.Head.DisplayName = "abc") |> shouldEqual true + true |> should equal true + +[] +let ``Symbol based find function from var`` () = + let input = + """ +type Test() = + let abc a b c = a + b + c + let test = """ + + // Split the input & define file name + let inputLines = input.Split('\n') + let file = "/home/user/Test.fsx" + let untyped, typeCheckResults = parseAndTypeCheckFileInProject(file, input) + + let decls = typeCheckResults.GetDeclarationSymbols(Some untyped, 4, 15, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + decls|> Seq .exists (fun d -> d.Head.DisplayName = "abc") |> shouldEqual true