@@ -14,6 +14,7 @@ open Microsoft.CodeAnalysis.Text
1414
1515open Microsoft.VisualStudio .FSharp .LanguageService
1616open Microsoft.FSharp .Compiler
17+ open Microsoft.FSharp .Compiler .Ast
1718open Microsoft.FSharp .Compiler .SourceCodeServices
1819open Microsoft.FSharp .Compiler .SourceCodeServices .ItemDescriptionIcons
1920
@@ -27,19 +28,18 @@ type internal LexerSymbolKind =
2728
2829type internal LexerSymbol =
2930{ Kind: LexerSymbolKind
30- Line: int
31- LeftColumn: int
32- RightColumn: int
33- Text: string
34- FileName: string }
35- member x.Range : Range.range =
36- Range.mkRange x.FileName( Range.mkPos( x.Line+ 1 ) x.LeftColumn) ( Range.mkPos( x.Line+ 1 ) x.RightColumn)
31+ /// Last part of `LongIdent`
32+ Ident: Ident
33+ /// All parts of `LongIdent`
34+ FullIsland: string list }
35+ member x.Range : Range.range = x.Ident.idRange
3736
3837[<RequireQualifiedAccess>]
3938type internal SymbolLookupKind =
40- | Fuzzy
41- | ByRightColumn
42- | ByLongIdent
39+ /// Position must lay inside symbol range.
40+ | Precise
41+ /// Position may lay one column outside of symbol range to the right.
42+ | Greedy
4343
4444module internal CommonHelpers =
4545type private SourceLineData ( lineStart : int , lexStateAtStartOfLine : FSharpTokenizerLexState , lexStateAtEndOfLine : FSharpTokenizerLexState ,
@@ -219,15 +219,17 @@ module internal CommonHelpers =
219219// and FullMathedLength (for "^type" which is tokenized as (INFIX_AT_HAT_OP, left=2) + (IDENT, left=3, length=4)
220220// we'll get (IDENT, left=2, length=5).
221221let tokens =
222+ let tokensCount = tokens.Length
222223 tokens
223- |> List.fold ( fun ( acc , lastToken ) ( token : FSharpTokenInfo ) ->
224+ |> List.foldi ( fun ( acc , lastToken ) index ( token : FSharpTokenInfo ) ->
224225match lastTokenwith
225226| Some twhen token.LeftColumn<= t.RightColumn-> acc, lastToken
226227| _ ->
228+ let isLastToken = index= tokensCount- 1
227229match tokenwith
228- | GenericTypeParameterPrefix-> acc, Some( DraftToken.Create LexerSymbolKind.GenericTypeParameter token)
229- | StaticallyResolvedTypeParameterPrefix-> acc, Some( DraftToken.Create LexerSymbolKind.StaticallyResolvedTypeParameter token)
230- | Other ->
230+ | GenericTypeParameterPrefixwhen not isLastToken -> acc, Some( DraftToken.Create LexerSymbolKind.GenericTypeParameter token)
231+ | StaticallyResolvedTypeParameterPrefixwhen not isLastToken -> acc, Some( DraftToken.Create LexerSymbolKind.StaticallyResolvedTypeParameter token)
232+ | _ ->
231233let draftToken =
232234match lastTokenwith
233235| Some{ Kind= LexerSymbolKind.GenericTypeParameter| LexerSymbolKind.StaticallyResolvedTypeParameteras kind} when isIdentifier token->
@@ -250,66 +252,34 @@ module internal CommonHelpers =
250252
251253// One or two tokens that in touch with the cursor (for "let x|(g) = ()" the tokens will be "x" and "(")
252254let tokensUnderCursor =
253- match lookupKindwith
254- | SymbolLookupKind.Fuzzy->
255- tokens|> List.filter( fun x -> x.Token.LeftColumn<= linePos.Character&& x.RightColumn+ 1 >= linePos.Character)
256- | SymbolLookupKind.ByRightColumn->
257- tokens|> List.filter( fun x -> x.RightColumn= linePos.Character)
258- | SymbolLookupKind.ByLongIdent->
259- tokens|> List.filter( fun x -> x.Token.LeftColumn<= linePos.Character)
260-
261- //printfn "Filtered tokens: %+A" tokensUnderCursor
262- match lookupKindwith
263- | SymbolLookupKind.ByLongIdent->
264- // Try to find start column of the long identifiers
265- // Assume that tokens are ordered in an decreasing order of start columns
266- let rec tryFindStartColumn tokens =
267- match tokenswith
268- | { DraftToken.Kind= LexerSymbolKind.Ident; Token= t1} :: { Kind= LexerSymbolKind.Operator; Token= t2} :: remainingTokens->
269- if t2.Tag= FSharpTokenTag.DOTthen
270- tryFindStartColumn remainingTokens
271- else
272- Some t1.LeftColumn
273- | { Kind= LexerSymbolKind.Ident; Token= t} :: _ ->
274- Some t.LeftColumn
275- | _ :: _ | [] ->
276- None
277- let decreasingTokens =
278- match tokensUnderCursor|> List.sortBy( fun token -> - token.Token.LeftColumn) with
279- // Skip the first dot if it is the start of the identifier
280- | { Kind= LexerSymbolKind.Operator; Token= t} :: remainingTokenswhen t.Tag= FSharpTokenTag.DOT->
281- remainingTokens
282- | newTokens-> newTokens
255+ let rightColumnCorrection =
256+ match lookupKindwith
257+ | SymbolLookupKind.Precise-> 0
258+ | SymbolLookupKind.Greedy-> 1
283259
284- match decreasingTokenswith
285- | [] -> None
286- | first:: _ ->
287- tryFindStartColumn decreasingTokens
288- |> Option.map( fun leftCol ->
289- { Kind= LexerSymbolKind.Ident
290- Line= linePos.Line
291- LeftColumn= leftCol
292- RightColumn= first.RightColumn+ 1
293- Text= lineStr.[ leftCol.. first.RightColumn]
294- FileName= fileName})
295- | SymbolLookupKind.Fuzzy
296- | SymbolLookupKind.ByRightColumn->
297- // Select IDENT token. If failed, select OPERATOR token.
298- tokensUnderCursor
299- |> List.tryFind( fun {DraftToken.Kind =k }->
300- match kwith
301- | LexerSymbolKind.Ident
302- | LexerSymbolKind.GenericTypeParameter
303- | LexerSymbolKind.StaticallyResolvedTypeParameter-> true
304- | _ -> false )
305- |> Option.orElseWith( fun _ -> tokensUnderCursor|> List.tryFind( fun {DraftToken.Kind =k }-> k= LexerSymbolKind.Operator))
306- |> Option.map( fun token ->
307- { Kind= token.Kind
308- Line= linePos.Line
309- LeftColumn= token.Token.LeftColumn
310- RightColumn= token.RightColumn+ 1
311- Text= lineStr.Substring( token.Token.LeftColumn, token.Token.FullMatchedLength)
312- FileName= fileName})
260+ tokens|> List.filter( fun x -> x.Token.LeftColumn<= linePos.Character&& ( x.RightColumn+ rightColumnCorrection) >= linePos.Character)
261+
262+ // Select IDENT token. If failed, select OPERATOR token.
263+ tokensUnderCursor
264+ |> List.tryFind( fun {DraftToken.Kind =k }->
265+ match kwith
266+ | LexerSymbolKind.Ident
267+ | LexerSymbolKind.GenericTypeParameter
268+ | LexerSymbolKind.StaticallyResolvedTypeParameter-> true
269+ | _ -> false )
270+ |> Option.orElseWith( fun _ -> tokensUnderCursor|> List.tryFind( fun {DraftToken.Kind =k }-> k= LexerSymbolKind.Operator))
271+ |> Option.map( fun token ->
272+ let plid , _ = QuickParse.GetPartialLongNameEx( lineStr, token.RightColumn)
273+ let identStr = lineStr.Substring( token.Token.LeftColumn, token.Token.FullMatchedLength)
274+ { Kind= token.Kind
275+ Ident=
276+ Ident
277+ ( identStr,
278+ Range.mkRange
279+ fileName
280+ ( Range.mkPos( linePos.Line+ 1 ) token.Token.LeftColumn)
281+ ( Range.mkPos( linePos.Line+ 1 ) ( token.RightColumn+ 1 )))
282+ FullIsland= plid@ [ identStr] })
313283
314284let private getCachedSourceLineData ( documentKey : DocumentId , sourceText : SourceText , position : int , fileName : string , defines : string list ) =
315285let textLine = sourceText.Lines.GetLineFromPosition( position)