@@ -46,168 +46,156 @@ type internal FSharpSignatureHelpProvider
4646
4747// Unit-testable core rutine
4848static member internal ProvideMethodsAsyncAux ( checker : FSharpChecker , documentationBuilder : IDocumentationBuilder , sourceText : SourceText , caretPosition : int , options : FSharpProjectOptions , triggerIsTypedChar : char option , filePath : string , textVersionHash : int ) = async {
49- let! results =
50- async {
51- match checker.TryGetRecentCheckResultsForFile( filePath, options) with
52- | Some( parseResults, checkFileResults, _) ->
53- return Some( parseResults, checkFileResults)
54- | None->
55- let! parseResults , checkFileAnswer = checker.ParseAndCheckFileInProject( filePath, textVersionHash, sourceText.ToString(), options)
56- match checkFileAnswerwith
57- | FSharpCheckFileAnswer.Aborted->
58- return None
59- | FSharpCheckFileAnswer.Succeeded( checkFileResults) ->
60- return Some( parseResults, checkFileResults)
61- }
62-
63- match resultswith
64- | None-> return None
65- | Some( parseResults, checkFileResults) ->
66- let textLines = sourceText.Lines
67- let caretLinePos = textLines.GetLinePosition( caretPosition)
68- let caretLineColumn = caretLinePos.Character
69-
70- // Get the parameter locations
71- let paramLocations = parseResults.FindNoteworthyParamInfoLocations( Pos.fromZ caretLinePos.Line caretLineColumn)
72-
73- match paramLocationswith
74- | None-> return None// no locations = no help
75- | Some nwpl->
76- let names = nwpl.LongId
77- let lidEnd = nwpl.LongIdEndLocation
78-
79- // Get the methods
80- let! methodGroup = checkFileResults.GetMethodsAlternate( lidEnd.Line, lidEnd.Column, " " , Some names)
81-
82- let methods = methodGroup.Methods
83-
84- if ( methods.Length= 0 || methodGroup.MethodName.EndsWith( " > )" )) then return Noneelse
85-
86- let isStaticArgTip =
87- let parenLine , parenCol = Pos.toZ nwpl.OpenParenLocation
88- assert ( parenLine< textLines.Count)
89- let parenLineText = textLines.[ parenLine]. ToString()
90- parenCol< parenLineText.Length&& parenLineText.[ parenCol] = '<'
91-
92- let filteredMethods =
93- [| for min methodsdo
94- if ( isStaticArgTip&& m.StaticParameters.Length> 0 ) ||
95- ( not isStaticArgTip&& m.HasParameters) then // need to distinguish TP<...>(...) angle brackets tip from parens tip
96- yield m|]
97-
98- if filteredMethods.Length= 0 then return Noneelse
99-
100- let posToLinePosition pos =
101- let ( l , c ) = Pos.toZ pos
102- // FSROSLYNTODO: FCS gives back line counts that are too large. Really, this shouldn't happen
103- let result = LinePosition( l, c)
104- let lastPosInDocument = textLines.GetLinePosition( textLines.[ textLines.Count-1 ]. End)
105- if lastPosInDocument.CompareTo( result) > 0 then resultelse lastPosInDocument
106-
107- // Compute the start position
108- let startPos = nwpl.LongIdStartLocation|> posToLinePosition
109-
110- // Compute the end position
111- let endPos =
112- let last = nwpl.TupleEndLocations.[ nwpl.TupleEndLocations.Length-1 ] |> posToLinePosition
113- ( if nwpl.IsThereACloseParenthen oneColBefore lastelse last)
114-
115- assert ( startPos.CompareTo( endPos) <= 0 )
116-
117- // Compute the applicable span between the parentheses
118- let applicableSpan =
119- textLines.GetTextSpan( LinePositionSpan( startPos, endPos))
120-
121- let startOfArgs = nwpl.OpenParenLocation|> posToLinePosition|> oneColAfter
122-
123- let tupleEnds =
124- [| yield startOfArgs
125- for iin 0 .. nwpl.TupleEndLocations.Length-2 do
126- yield nwpl.TupleEndLocations.[ i] |> posToLinePosition
127- yield endPos|]
128-
129- // If we are pressing "(" or "<" or ",", then only pop up the info if this is one of the actual, real detected positions in the detected promptable call
130- //
131- // For example the last "(" in
132- // List.map (fun a -> (
133- // should not result in a prompt.
134- //
135- // Likewise the last "," in
136- // Console.WriteLine( [(1,
137- // should not result in a prompt, whereas this one will:
138- // Console.WriteLine( [(1,2)],
139-
140- match triggerIsTypedCharwith
141- | Some( '<' | '(' | ',' ) when not ( tupleEnds|> Array.exists( fun lp -> lp.Character= caretLineColumn)) ->
142- return None// comma or paren at wrong location = remove help display
143- | _ ->
144-
145- // Compute the argument index by working out where the caret is between the various commas.
146- let argumentIndex =
147- let computedTextSpans =
148- tupleEnds
149- |> Array.pairwise
150- |> Array.map( fun ( lp1 , lp2 ) -> textLines.GetTextSpan( LinePositionSpan( lp1, lp2)))
151-
152- match ( computedTextSpans|> Array.tryFindIndex( fun t -> t.Contains( caretPosition))) with
153- | None->
154- // Because 'TextSpan.Contains' only succeeeds if 'TextSpan.Start <= caretPosition < TextSpan.End' is true,
155- // we need to check if the caret is at the very last position in the TextSpan.
156- //
157- // We default to 0, which is the first argument, if the caret position was nowhere to be found.
158- if computedTextSpans.[ computedTextSpans.Length-1 ]. End= caretPositionthen
159- computedTextSpans.Length-1
160- else 0
161- | Some n-> n
162-
163- // Compute the overall argument count
164- let argumentCount =
165- match nwpl.TupleEndLocations.Lengthwith
166- | 1 when caretLinePos.Character= startOfArgs.Character-> 0 // count "WriteLine(" as zero arguments
167- | n-> n
168-
169- // Compute the current argument name, if any
170- let argumentName =
171- if argumentIndex< nwpl.NamedParamNames.Lengththen
172- nwpl.NamedParamNames.[ argumentIndex]
173- else
174- None// not a named argument
175-
176- // Prepare the results
177- let results = List<_>()
178-
179- for methodin methodsdo
180- // Create the documentation. Note, do this on the background thread, since doing it in the documentationBuild fails to build the XML index
181- let mainDescription = List()
182- let documentation = List()
183- XmlDocumentation.BuildMethodOverloadTipText( documentationBuilder, CommonRoslynHelpers.CollectTaggedText mainDescription, CommonRoslynHelpers.CollectTaggedText documentation, method.StructuredDescription, false )
184-
185- let parameters =
186- let parameters = if isStaticArgTipthen method.StaticParameterselse method.Parameters
187- [| for pin parametersdo
188- let doc = List()
189- // FSROSLYNTODO: compute the proper help text for parameters, c.f. AppendParameter in XmlDocumentation.fs
190- XmlDocumentation.BuildMethodParamText( documentationBuilder, CommonRoslynHelpers.CollectTaggedText doc, method.XmlDoc, p.ParameterName)
191- let parts = List()
192- renderL( taggedTextListR( CommonRoslynHelpers.CollectTaggedText parts)) p.StructuredDisplay|> ignore
193- yield ( p.ParameterName, p.IsOptional, doc, parts)
194- |]
195-
196- let prefixParts =
197- [| TaggedText( TextTags.Method, methodGroup.MethodName);
198- TaggedText( TextTags.Punctuation, ( if isStaticArgTipthen " <" else " (" )) |]
199- let separatorParts = [| TaggedText( TextTags.Punctuation, " ," ); TaggedText( TextTags.Space, " " ) |]
200- let suffixParts = [| TaggedText( TextTags.Punctuation, ( if isStaticArgTipthen " >" else " )" )) |]
201-
202- let completionItem = ( method.HasParamArrayArg, documentation, prefixParts, separatorParts, suffixParts, parameters, mainDescription)
203- // FSROSLYNTODO: Do we need a cache like for completion?
204- //declarationItemsCache.Remove(completionItem.DisplayText) |> ignore // clear out stale entries if they exist
205- //declarationItemsCache.Add(completionItem.DisplayText, declarationItem)
206- results.Add( completionItem)
207-
208-
209- let items = ( results.ToArray(), applicableSpan, argumentIndex, argumentCount, argumentName)
210- return Some items
49+ let! parseResults , checkFileAnswer = checker.ParseAndCheckFileInProject( filePath, textVersionHash, sourceText.ToString(), options)
50+ match checkFileAnswerwith
51+ | FSharpCheckFileAnswer.Aborted-> return None
52+ | FSharpCheckFileAnswer.Succeeded( checkFileResults) ->
53+
54+ let textLines = sourceText.Lines
55+ let caretLinePos = textLines.GetLinePosition( caretPosition)
56+ let caretLineColumn = caretLinePos.Character
57+
58+ // Get the parameter locations
59+ let paramLocations = parseResults.FindNoteworthyParamInfoLocations( Pos.fromZ caretLinePos.Line caretLineColumn)
60+
61+ match paramLocationswith
62+ | None-> return None// no locations = no help
63+ | Some nwpl->
64+ let names = nwpl.LongId
65+ let lidEnd = nwpl.LongIdEndLocation
66+
67+ // Get the methods
68+ let! methodGroup = checkFileResults.GetMethodsAlternate( lidEnd.Line, lidEnd.Column, " " , Some names)
69+
70+ let methods = methodGroup.Methods
71+
72+ if ( methods.Length= 0 || methodGroup.MethodName.EndsWith( " > )" )) then return Noneelse
73+
74+ let isStaticArgTip =
75+ let parenLine , parenCol = Pos.toZ nwpl.OpenParenLocation
76+ assert ( parenLine< textLines.Count)
77+ let parenLineText = textLines.[ parenLine]. ToString()
78+ parenCol< parenLineText.Length&& parenLineText.[ parenCol] = '<'
79+
80+ let filteredMethods =
81+ [| for min methodsdo
82+ if ( isStaticArgTip&& m.StaticParameters.Length> 0 ) ||
83+ ( not isStaticArgTip&& m.HasParameters) then // need to distinguish TP<...>(...) angle brackets tip from parens tip
84+ yield m|]
85+
86+ if filteredMethods.Length= 0 then return Noneelse
87+
88+ let posToLinePosition pos =
89+ let ( l , c ) = Pos.toZ pos
90+ // FSROSLYNTODO: FCS gives back line counts that are too large. Really, this shouldn't happen
91+ let result = LinePosition( l, c)
92+ let lastPosInDocument = textLines.GetLinePosition( textLines.[ textLines.Count-1 ]. End)
93+ if lastPosInDocument.CompareTo( result) > 0 then resultelse lastPosInDocument
94+
95+ // Compute the start position
96+ let startPos = nwpl.LongIdStartLocation|> posToLinePosition
97+
98+ // Compute the end position
99+ let endPos =
100+ let last = nwpl.TupleEndLocations.[ nwpl.TupleEndLocations.Length-1 ] |> posToLinePosition
101+ ( if nwpl.IsThereACloseParenthen oneColBefore lastelse last)
102+
103+ assert ( startPos.CompareTo( endPos) <= 0 )
104+
105+ // Compute the applicable span between the parentheses
106+ let applicableSpan =
107+ textLines.GetTextSpan( LinePositionSpan( startPos, endPos))
108+
109+ let startOfArgs = nwpl.OpenParenLocation|> posToLinePosition|> oneColAfter
110+
111+ let tupleEnds =
112+ [| yield startOfArgs
113+ for iin 0 .. nwpl.TupleEndLocations.Length-2 do
114+ yield nwpl.TupleEndLocations.[ i] |> posToLinePosition
115+ yield endPos|]
116+
117+ // If we are pressing "(" or "<" or ",", then only pop up the info if this is one of the actual, real detected positions in the detected promptable call
118+ //
119+ // For example the last "(" in
120+ // List.map (fun a -> (
121+ // should not result in a prompt.
122+ //
123+ // Likewise the last "," in
124+ // Console.WriteLine( [(1,
125+ // should not result in a prompt, whereas this one will:
126+ // Console.WriteLine( [(1,2)],
127+
128+ match triggerIsTypedCharwith
129+ | Some( '<' | '(' | ',' ) when not ( tupleEnds|> Array.exists( fun lp -> lp.Character= caretLineColumn)) ->
130+ return None// comma or paren at wrong location = remove help display
131+ | _ ->
132+
133+ // Compute the argument index by working out where the caret is between the various commas.
134+ let argumentIndex =
135+ let computedTextSpans =
136+ tupleEnds
137+ |> Array.pairwise
138+ |> Array.map( fun ( lp1 , lp2 ) -> textLines.GetTextSpan( LinePositionSpan( lp1, lp2)))
139+
140+ match ( computedTextSpans|> Array.tryFindIndex( fun t -> t.Contains( caretPosition))) with
141+ | None->
142+ // Because 'TextSpan.Contains' only succeeeds if 'TextSpan.Start <= caretPosition < TextSpan.End' is true,
143+ // we need to check if the caret is at the very last position in the TextSpan.
144+ //
145+ // We default to 0, which is the first argument, if the caret position was nowhere to be found.
146+ if computedTextSpans.[ computedTextSpans.Length-1 ]. End= caretPositionthen
147+ computedTextSpans.Length-1
148+ else 0
149+ | Some n-> n
150+
151+ // Compute the overall argument count
152+ let argumentCount =
153+ match nwpl.TupleEndLocations.Lengthwith
154+ | 1 when caretLinePos.Character= startOfArgs.Character-> 0 // count "WriteLine(" as zero arguments
155+ | n-> n
156+
157+ // Compute the current argument name, if any
158+ let argumentName =
159+ if argumentIndex< nwpl.NamedParamNames.Lengththen
160+ nwpl.NamedParamNames.[ argumentIndex]
161+ else
162+ None// not a named argument
163+
164+ // Prepare the results
165+ let results = List<_>()
166+
167+ for methodin methodsdo
168+ // Create the documentation. Note, do this on the background thread, since doing it in the documentationBuild fails to build the XML index
169+ let mainDescription = List()
170+ let documentation = List()
171+ XmlDocumentation.BuildMethodOverloadTipText( documentationBuilder, CommonRoslynHelpers.CollectTaggedText mainDescription, CommonRoslynHelpers.CollectTaggedText documentation, method.StructuredDescription, false )
172+
173+ let parameters =
174+ let parameters = if isStaticArgTipthen method.StaticParameterselse method.Parameters
175+ [| for pin parametersdo
176+ let doc = List()
177+ // FSROSLYNTODO: compute the proper help text for parameters, c.f. AppendParameter in XmlDocumentation.fs
178+ XmlDocumentation.BuildMethodParamText( documentationBuilder, CommonRoslynHelpers.CollectTaggedText doc, method.XmlDoc, p.ParameterName)
179+ let parts = List()
180+ renderL( taggedTextListR( CommonRoslynHelpers.CollectTaggedText parts)) p.StructuredDisplay|> ignore
181+ yield ( p.ParameterName, p.IsOptional, doc, parts)
182+ |]
183+
184+ let prefixParts =
185+ [| TaggedText( TextTags.Method, methodGroup.MethodName);
186+ TaggedText( TextTags.Punctuation, ( if isStaticArgTipthen " <" else " (" )) |]
187+ let separatorParts = [| TaggedText( TextTags.Punctuation, " ," ); TaggedText( TextTags.Space, " " ) |]
188+ let suffixParts = [| TaggedText( TextTags.Punctuation, ( if isStaticArgTipthen " >" else " )" )) |]
189+
190+ let completionItem = ( method.HasParamArrayArg, documentation, prefixParts, separatorParts, suffixParts, parameters, mainDescription)
191+ // FSROSLYNTODO: Do we need a cache like for completion?
192+ //declarationItemsCache.Remove(completionItem.DisplayText) |> ignore // clear out stale entries if they exist
193+ //declarationItemsCache.Add(completionItem.DisplayText, declarationItem)
194+ results.Add( completionItem)
195+
196+
197+ let items = ( results.ToArray(), applicableSpan, argumentIndex, argumentCount, argumentName)
198+ return Some items
211199}
212200
213201interface ISignatureHelpProviderwith