提交 58e3753a 编写于 作者: V Vasily Kirichenko 提交者: Kevin Ransom (msft)

Refactor QuickInfoProvider (#2778)

* refactor QuickInfoProvider

* fix wrong "this symbol range"

* further refactoring
上级 720f0b4d
......@@ -101,7 +101,7 @@ type internal FSharpAddOpenCodeFixProvider
let! symbol =
asyncMaybe {
let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, context.Span.End, document.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, context.Span.End, document.FilePath, defines, SymbolLookupKind.Greedy)
return! checkResults.GetSymbolUseAtLocation(Line.fromZ linePos.Line, lexerSymbol.Ident.idRange.EndColumn, line.ToString(), lexerSymbol.FullIsland)
} |> liftAsync
......
......@@ -169,7 +169,7 @@ type internal FSharpImplementInterfaceCodeFixProvider
| _ ->
Some context.Span.End
let! interfaceState = queryInterfaceState appendBracketAt interfacePos tokens parsedInput
let! symbol = Tokenizer.getSymbolAtPosition(context.Document.Id, sourceText, fixupPosition, context.Document.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! symbol = Tokenizer.getSymbolAtPosition(context.Document.Id, sourceText, fixupPosition, context.Document.FilePath, defines, SymbolLookupKind.Greedy)
let fcsTextLineNumber = textLine.LineNumber + 1
let lineContents = textLine.ToString()
let! options = context.Document.GetOptionsAsync(cancellationToken)
......
......@@ -13,10 +13,9 @@ open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Layout
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.VisualStudio.FSharp.LanguageService
[<RequireQualifiedAccess>]
module internal RoslynHelpers =
let FSharpRangeToTextSpan(sourceText: SourceText, range: range) =
......
......@@ -21,8 +21,6 @@ open Microsoft.VisualStudio.Shell.Interop
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices
open Tokenizer
type internal FSharpCompletionProvider
(
......@@ -142,7 +140,7 @@ type internal FSharpCompletionProvider
let maxHints = if mruItems.Values.Count = 0 then 0 else Seq.max mruItems.Values
sortedDeclItems |> Array.iteri (fun number declItem ->
let glyph = FSharpGlyphToRoslynGlyph (declItem.Glyph, declItem.Accessibility)
let glyph = Tokenizer.FSharpGlyphToRoslynGlyph (declItem.Glyph, declItem.Accessibility)
let name =
match entityKind, declItem.NamespaceToOpen with
| Some EntityKind.Attribute, _ when declItem.IsAttribute && declItem.Name.EndsWith "Attribute" ->
......
......@@ -56,7 +56,7 @@ type internal FSharpDocumentHighlightsService [<ImportingConstructor>] (checkerP
let textLine = sourceText.Lines.GetLineFromPosition(position)
let textLinePos = sourceText.Lines.GetLinePosition(position)
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let! symbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! symbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, SymbolLookupKind.Greedy)
let! _, _, checkFileResults = checker.ParseAndCheckDocument(filePath, textVersionHash, sourceText.ToString(), options, allowStaleResults = true)
let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland)
let! symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) |> liftAsync
......
......@@ -17,10 +17,8 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices
open Tokenizer
open Symbols
type internal FailureInlineRenameInfo private () =
interface IInlineRenameInfo with
member __.CanRename = false
......
......@@ -8,15 +8,11 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices
open Tokenizer
open Symbols
open TypedAstUtils
type CheckResults =
| Ready of (FSharpParseFileResults * FSharpCheckFileResults) option
| StillRunning of Async<(FSharpParseFileResults * FSharpCheckFileResults) option>
type FSharpChecker with
member this.ParseDocument(document: Document, options: FSharpProjectOptions, sourceText: string) =
......
......@@ -65,7 +65,7 @@ module internal SymbolHelpers =
do! Option.guard (originalText.Length > 0)
let! options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject document
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, symbolSpan.Start, document.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, symbolSpan.Start, document.FilePath, defines, SymbolLookupKind.Greedy)
let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, allowStaleResults = true)
let textLine = sourceText.Lines.GetLineFromPosition(symbolSpan.Start)
let textLinePos = sourceText.Lines.GetLinePosition(symbolSpan.Start)
......
module internal Microsoft.VisualStudio.FSharp.Editor.Tokenizer
namespace Microsoft.VisualStudio.FSharp.Editor
open System
open System.Collections.Generic
......@@ -17,7 +17,7 @@ open Microsoft.FSharp.Compiler.SourceCodeServices
[<RequireQualifiedAccess>]
type LexerSymbolKind =
type internal LexerSymbolKind =
| Ident
| Operator
| Punctuation
......@@ -25,7 +25,7 @@ type LexerSymbolKind =
| StaticallyResolvedTypeParameter
| Other
type LexerSymbol =
type internal LexerSymbol =
{ Kind: LexerSymbolKind
/// Last part of `LongIdent`
Ident: Ident
......@@ -34,554 +34,555 @@ type LexerSymbol =
member x.Range: Range.range = x.Ident.idRange
[<RequireQualifiedAccess>]
type SymbolLookupKind =
type internal SymbolLookupKind =
/// Position must lay inside symbol range.
| Precise
/// Position may lay one column outside of symbol range to the right.
| Greedy
[<RequireQualifiedAccess>]
module internal Tokenizer =
let (|Public|Internal|Protected|Private|) (a: FSharpAccessibility option) =
match a with
| None -> Public
| Some a ->
if a.IsPublic then Public
elif a.IsInternal then Internal
elif a.IsPrivate then Private
else Protected
let inline (|Public|Internal|Protected|Private|) (a: FSharpAccessibility option) =
match a with
| None -> Public
| Some a ->
if a.IsPublic then Public
elif a.IsInternal then Internal
elif a.IsPrivate then Private
else Protected
let FSharpGlyphToRoslynGlyph (glyph: FSharpGlyph, accessibility: FSharpAccessibility option) =
match glyph with
| FSharpGlyph.Class
| FSharpGlyph.Exception
| FSharpGlyph.Typedef
| FSharpGlyph.Type ->
match accessibility with
| Public -> Glyph.ClassPublic
| Internal -> Glyph.ClassInternal
| Protected -> Glyph.ClassProtected
| Private -> Glyph.ClassPrivate
| FSharpGlyph.Constant ->
match accessibility with
| Public -> Glyph.ConstantPublic
| Internal -> Glyph.ConstantInternal
| Protected -> Glyph.ConstantProtected
| Private -> Glyph.ConstantPrivate
| FSharpGlyph.Delegate ->
match accessibility with
| Public -> Glyph.DelegatePublic
| Internal -> Glyph.DelegateInternal
| Protected -> Glyph.DelegateProtected
| Private -> Glyph.DelegatePrivate
| FSharpGlyph.Enum
| FSharpGlyph.Union ->
match accessibility with
| Public -> Glyph.EnumPublic
| Internal -> Glyph.EnumInternal
| Protected -> Glyph.EnumProtected
| Private -> Glyph.EnumPrivate
| FSharpGlyph.EnumMember -> Glyph.EnumMember
| FSharpGlyph.Event ->
match accessibility with
| Public -> Glyph.EventPublic
| Internal -> Glyph.EventInternal
| Protected -> Glyph.EventProtected
| Private -> Glyph.EventPrivate
| FSharpGlyph.Field ->
match accessibility with
| Public -> Glyph.FieldPublic
| Internal -> Glyph.FieldInternal
| Protected -> Glyph.FieldProtected
| Private -> Glyph.FieldPrivate
| FSharpGlyph.Interface ->
match accessibility with
| Public -> Glyph.InterfacePublic
| Internal -> Glyph.InterfaceInternal
| Protected -> Glyph.InterfaceProtected
| Private -> Glyph.InterfacePrivate
| FSharpGlyph.Method
| FSharpGlyph.OverridenMethod ->
match accessibility with
| Public -> Glyph.MethodPublic
| Internal -> Glyph.MethodInternal
| Protected -> Glyph.MethodProtected
| Private -> Glyph.MethodPrivate
| FSharpGlyph.ExtensionMethod ->
match accessibility with
| Public -> Glyph.ExtensionMethodPublic
| Internal -> Glyph.ExtensionMethodInternal
| Protected -> Glyph.ExtensionMethodProtected
| Private -> Glyph.ExtensionMethodPrivate
| FSharpGlyph.Module ->
match accessibility with
| Public -> Glyph.ModulePublic
| Internal -> Glyph.ModuleInternal
| Protected -> Glyph.ModuleProtected
| Private -> Glyph.ModulePrivate
| FSharpGlyph.NameSpace -> Glyph.Namespace
| FSharpGlyph.Property ->
match accessibility with
| Public -> Glyph.PropertyPublic
| Internal -> Glyph.PropertyInternal
| Protected -> Glyph.PropertyProtected
| Private -> Glyph.PropertyPrivate
| FSharpGlyph.Struct ->
match accessibility with
| Public -> Glyph.StructurePublic
| Internal -> Glyph.StructureInternal
| Protected -> Glyph.StructureProtected
| Private -> Glyph.StructurePrivate
| FSharpGlyph.Variable -> Glyph.Local
| FSharpGlyph.Error -> Glyph.Error
let GetGlyphForSymbol (symbol: FSharpSymbol, kind: LexerSymbolKind) =
match kind with
| LexerSymbolKind.Operator -> Glyph.Operator
| _ ->
match symbol with
| :? FSharpUnionCase as x ->
match Some x.Accessibility with
| Public -> Glyph.EnumPublic
| Internal -> Glyph.EnumInternal
| Protected -> Glyph.EnumProtected
| Private -> Glyph.EnumPrivate
| :? FSharpActivePatternCase -> Glyph.EnumPublic
| :? FSharpField as x ->
if x.IsLiteral then
match Some x.Accessibility with
let FSharpGlyphToRoslynGlyph (glyph: FSharpGlyph, accessibility: FSharpAccessibility option) =
match glyph with
| FSharpGlyph.Class
| FSharpGlyph.Exception
| FSharpGlyph.Typedef
| FSharpGlyph.Type ->
match accessibility with
| Public -> Glyph.ClassPublic
| Internal -> Glyph.ClassInternal
| Protected -> Glyph.ClassProtected
| Private -> Glyph.ClassPrivate
| FSharpGlyph.Constant ->
match accessibility with
| Public -> Glyph.ConstantPublic
| Internal -> Glyph.ConstantInternal
| Protected -> Glyph.ConstantProtected
| Private -> Glyph.ConstantPrivate
else
match Some x.Accessibility with
| FSharpGlyph.Delegate ->
match accessibility with
| Public -> Glyph.DelegatePublic
| Internal -> Glyph.DelegateInternal
| Protected -> Glyph.DelegateProtected
| Private -> Glyph.DelegatePrivate
| FSharpGlyph.Enum
| FSharpGlyph.Union ->
match accessibility with
| Public -> Glyph.EnumPublic
| Internal -> Glyph.EnumInternal
| Protected -> Glyph.EnumProtected
| Private -> Glyph.EnumPrivate
| FSharpGlyph.EnumMember -> Glyph.EnumMember
| FSharpGlyph.Event ->
match accessibility with
| Public -> Glyph.EventPublic
| Internal -> Glyph.EventInternal
| Protected -> Glyph.EventProtected
| Private -> Glyph.EventPrivate
| FSharpGlyph.Field ->
match accessibility with
| Public -> Glyph.FieldPublic
| Internal -> Glyph.FieldInternal
| Protected -> Glyph.FieldProtected
| Private -> Glyph.FieldPrivate
| :? FSharpParameter -> Glyph.Parameter
| :? FSharpMemberOrFunctionOrValue as x ->
if x.LiteralValue.IsSome then
match Some x.Accessibility with
| Public -> Glyph.ConstantPublic
| Internal -> Glyph.ConstantInternal
| Protected -> Glyph.ConstantProtected
| Private -> Glyph.ConstantPrivate
elif x.IsExtensionMember then
match Some x.Accessibility with
| Public -> Glyph.ExtensionMethodPublic
| Internal -> Glyph.ExtensionMethodInternal
| Protected -> Glyph.ExtensionMethodProtected
| Private -> Glyph.ExtensionMethodPrivate
elif x.IsProperty || x.IsPropertyGetterMethod || x.IsPropertySetterMethod then
match Some x.Accessibility with
| Public -> Glyph.PropertyPublic
| Internal -> Glyph.PropertyInternal
| Protected -> Glyph.PropertyProtected
| Private -> Glyph.PropertyPrivate
elif x.IsEvent then
match Some x.Accessibility with
| Public -> Glyph.EventPublic
| Internal -> Glyph.EventInternal
| Protected -> Glyph.EventProtected
| Private -> Glyph.EventPrivate
else
match Some x.Accessibility with
| Public -> Glyph.MethodPublic
| Internal -> Glyph.MethodInternal
| Protected -> Glyph.MethodProtected
| Private -> Glyph.MethodPrivate
| :? FSharpEntity as x ->
if x.IsValueType then
match Some x.Accessibility with
| Public -> Glyph.StructurePublic
| Internal -> Glyph.StructureInternal
| Protected -> Glyph.StructureProtected
| Private -> Glyph.StructurePrivate
elif x.IsFSharpModule then
match Some x.Accessibility with
| Public -> Glyph.ModulePublic
| Internal -> Glyph.ModuleInternal
| Protected -> Glyph.ModuleProtected
| Private -> Glyph.ModulePrivate
elif x.IsEnum || x.IsFSharpUnion then
| FSharpGlyph.Interface ->
match accessibility with
| Public -> Glyph.InterfacePublic
| Internal -> Glyph.InterfaceInternal
| Protected -> Glyph.InterfaceProtected
| Private -> Glyph.InterfacePrivate
| FSharpGlyph.Method
| FSharpGlyph.OverridenMethod ->
match accessibility with
| Public -> Glyph.MethodPublic
| Internal -> Glyph.MethodInternal
| Protected -> Glyph.MethodProtected
| Private -> Glyph.MethodPrivate
| FSharpGlyph.ExtensionMethod ->
match accessibility with
| Public -> Glyph.ExtensionMethodPublic
| Internal -> Glyph.ExtensionMethodInternal
| Protected -> Glyph.ExtensionMethodProtected
| Private -> Glyph.ExtensionMethodPrivate
| FSharpGlyph.Module ->
match accessibility with
| Public -> Glyph.ModulePublic
| Internal -> Glyph.ModuleInternal
| Protected -> Glyph.ModuleProtected
| Private -> Glyph.ModulePrivate
| FSharpGlyph.NameSpace -> Glyph.Namespace
| FSharpGlyph.Property ->
match accessibility with
| Public -> Glyph.PropertyPublic
| Internal -> Glyph.PropertyInternal
| Protected -> Glyph.PropertyProtected
| Private -> Glyph.PropertyPrivate
| FSharpGlyph.Struct ->
match accessibility with
| Public -> Glyph.StructurePublic
| Internal -> Glyph.StructureInternal
| Protected -> Glyph.StructureProtected
| Private -> Glyph.StructurePrivate
| FSharpGlyph.Variable -> Glyph.Local
| FSharpGlyph.Error -> Glyph.Error
let GetGlyphForSymbol (symbol: FSharpSymbol, kind: LexerSymbolKind) =
match kind with
| LexerSymbolKind.Operator -> Glyph.Operator
| _ ->
match symbol with
| :? FSharpUnionCase as x ->
match Some x.Accessibility with
| Public -> Glyph.EnumPublic
| Internal -> Glyph.EnumInternal
| Protected -> Glyph.EnumProtected
| Private -> Glyph.EnumPrivate
elif x.IsInterface then
| :? FSharpActivePatternCase -> Glyph.EnumPublic
| :? FSharpField as x ->
if x.IsLiteral then
match Some x.Accessibility with
| Public -> Glyph.InterfacePublic
| Internal -> Glyph.InterfaceInternal
| Protected -> Glyph.InterfaceProtected
| Private -> Glyph.InterfacePrivate
elif x.IsDelegate then
match Some x.Accessibility with
| Public -> Glyph.DelegatePublic
| Internal -> Glyph.DelegateInternal
| Protected -> Glyph.DelegateProtected
| Private -> Glyph.DelegatePrivate
elif x.IsNamespace then
Glyph.Namespace
| Public -> Glyph.ConstantPublic
| Internal -> Glyph.ConstantInternal
| Protected -> Glyph.ConstantProtected
| Private -> Glyph.ConstantPrivate
else
match Some x.Accessibility with
| Public -> Glyph.ClassPublic
| Internal -> Glyph.ClassInternal
| Protected -> Glyph.ClassProtected
| Private -> Glyph.ClassPrivate
| _ -> Glyph.None
| Public -> Glyph.FieldPublic
| Internal -> Glyph.FieldInternal
| Protected -> Glyph.FieldProtected
| Private -> Glyph.FieldPrivate
| :? FSharpParameter -> Glyph.Parameter
| :? FSharpMemberOrFunctionOrValue as x ->
if x.LiteralValue.IsSome then
match Some x.Accessibility with
| Public -> Glyph.ConstantPublic
| Internal -> Glyph.ConstantInternal
| Protected -> Glyph.ConstantProtected
| Private -> Glyph.ConstantPrivate
elif x.IsExtensionMember then
match Some x.Accessibility with
| Public -> Glyph.ExtensionMethodPublic
| Internal -> Glyph.ExtensionMethodInternal
| Protected -> Glyph.ExtensionMethodProtected
| Private -> Glyph.ExtensionMethodPrivate
elif x.IsProperty || x.IsPropertyGetterMethod || x.IsPropertySetterMethod then
match Some x.Accessibility with
| Public -> Glyph.PropertyPublic
| Internal -> Glyph.PropertyInternal
| Protected -> Glyph.PropertyProtected
| Private -> Glyph.PropertyPrivate
elif x.IsEvent then
match Some x.Accessibility with
| Public -> Glyph.EventPublic
| Internal -> Glyph.EventInternal
| Protected -> Glyph.EventProtected
| Private -> Glyph.EventPrivate
else
match Some x.Accessibility with
| Public -> Glyph.MethodPublic
| Internal -> Glyph.MethodInternal
| Protected -> Glyph.MethodProtected
| Private -> Glyph.MethodPrivate
| :? FSharpEntity as x ->
if x.IsValueType then
match Some x.Accessibility with
| Public -> Glyph.StructurePublic
| Internal -> Glyph.StructureInternal
| Protected -> Glyph.StructureProtected
| Private -> Glyph.StructurePrivate
elif x.IsFSharpModule then
match Some x.Accessibility with
| Public -> Glyph.ModulePublic
| Internal -> Glyph.ModuleInternal
| Protected -> Glyph.ModuleProtected
| Private -> Glyph.ModulePrivate
elif x.IsEnum || x.IsFSharpUnion then
match Some x.Accessibility with
| Public -> Glyph.EnumPublic
| Internal -> Glyph.EnumInternal
| Protected -> Glyph.EnumProtected
| Private -> Glyph.EnumPrivate
elif x.IsInterface then
match Some x.Accessibility with
| Public -> Glyph.InterfacePublic
| Internal -> Glyph.InterfaceInternal
| Protected -> Glyph.InterfaceProtected
| Private -> Glyph.InterfacePrivate
elif x.IsDelegate then
match Some x.Accessibility with
| Public -> Glyph.DelegatePublic
| Internal -> Glyph.DelegateInternal
| Protected -> Glyph.DelegateProtected
| Private -> Glyph.DelegatePrivate
elif x.IsNamespace then
Glyph.Namespace
else
match Some x.Accessibility with
| Public -> Glyph.ClassPublic
| Internal -> Glyph.ClassInternal
| Protected -> Glyph.ClassProtected
| Private -> Glyph.ClassPrivate
| _ -> Glyph.None
type private SourceLineData(lineStart: int, lexStateAtStartOfLine: FSharpTokenizerLexState, lexStateAtEndOfLine: FSharpTokenizerLexState,
hashCode: int, classifiedSpans: IReadOnlyList<ClassifiedSpan>, tokens: FSharpTokenInfo list) =
member val LineStart = lineStart
member val LexStateAtStartOfLine = lexStateAtStartOfLine
member val LexStateAtEndOfLine = lexStateAtEndOfLine
member val HashCode = hashCode
member val ClassifiedSpans = classifiedSpans
member val Tokens = tokens
type private SourceLineData(lineStart: int, lexStateAtStartOfLine: FSharpTokenizerLexState, lexStateAtEndOfLine: FSharpTokenizerLexState,
hashCode: int, classifiedSpans: IReadOnlyList<ClassifiedSpan>, tokens: FSharpTokenInfo list) =
member val LineStart = lineStart
member val LexStateAtStartOfLine = lexStateAtStartOfLine
member val LexStateAtEndOfLine = lexStateAtEndOfLine
member val HashCode = hashCode
member val ClassifiedSpans = classifiedSpans
member val Tokens = tokens
member data.IsValid(textLine: TextLine) =
data.LineStart = textLine.Start &&
let lineContents = textLine.Text.ToString(textLine.Span)
data.HashCode = lineContents.GetHashCode()
member data.IsValid(textLine: TextLine) =
data.LineStart = textLine.Start &&
let lineContents = textLine.Text.ToString(textLine.Span)
data.HashCode = lineContents.GetHashCode()
type private SourceTextData(approxLines: int) =
let data = ResizeArray<SourceLineData option>(approxLines)
let extendTo i =
if i >= data.Count then
data.Capacity <- i + 1
for j in data.Count .. i do
data.Add(None)
member x.Item
with get (i:int) = extendTo i; data.[i]
and set (i:int) v = extendTo i; data.[i] <- v
type private SourceTextData(approxLines: int) =
let data = ResizeArray<SourceLineData option>(approxLines)
let extendTo i =
if i >= data.Count then
data.Capacity <- i + 1
for j in data.Count .. i do
data.Add(None)
member x.Item
with get (i:int) = extendTo i; data.[i]
and set (i:int) v = extendTo i; data.[i] <- v
member x.ClearFrom(n) =
let mutable i = n
while i < data.Count && data.[i].IsSome do
data.[i] <- None
i <- i + 1
member x.ClearFrom(n) =
let mutable i = n
while i < data.Count && data.[i].IsSome do
data.[i] <- None
i <- i + 1
let private dataCache = ConditionalWeakTable<DocumentId, SourceTextData>()
let private dataCache = ConditionalWeakTable<DocumentId, SourceTextData>()
let compilerTokenToRoslynToken(colorKind: FSharpTokenColorKind) : string =
match colorKind with
| FSharpTokenColorKind.Comment -> ClassificationTypeNames.Comment
| FSharpTokenColorKind.Identifier -> ClassificationTypeNames.Identifier
| FSharpTokenColorKind.Keyword -> ClassificationTypeNames.Keyword
| FSharpTokenColorKind.String -> ClassificationTypeNames.StringLiteral
| FSharpTokenColorKind.Text -> ClassificationTypeNames.Text
| FSharpTokenColorKind.UpperIdentifier -> ClassificationTypeNames.Identifier
| FSharpTokenColorKind.Number -> ClassificationTypeNames.NumericLiteral
| FSharpTokenColorKind.InactiveCode -> ClassificationTypeNames.ExcludedCode
| FSharpTokenColorKind.PreprocessorKeyword -> ClassificationTypeNames.PreprocessorKeyword
| FSharpTokenColorKind.Operator -> ClassificationTypeNames.Operator
| FSharpTokenColorKind.Punctuation -> ClassificationTypeNames.Punctuation
| FSharpTokenColorKind.Default | _ -> ClassificationTypeNames.Text
let compilerTokenToRoslynToken(colorKind: FSharpTokenColorKind) : string =
match colorKind with
| FSharpTokenColorKind.Comment -> ClassificationTypeNames.Comment
| FSharpTokenColorKind.Identifier -> ClassificationTypeNames.Identifier
| FSharpTokenColorKind.Keyword -> ClassificationTypeNames.Keyword
| FSharpTokenColorKind.String -> ClassificationTypeNames.StringLiteral
| FSharpTokenColorKind.Text -> ClassificationTypeNames.Text
| FSharpTokenColorKind.UpperIdentifier -> ClassificationTypeNames.Identifier
| FSharpTokenColorKind.Number -> ClassificationTypeNames.NumericLiteral
| FSharpTokenColorKind.InactiveCode -> ClassificationTypeNames.ExcludedCode
| FSharpTokenColorKind.PreprocessorKeyword -> ClassificationTypeNames.PreprocessorKeyword
| FSharpTokenColorKind.Operator -> ClassificationTypeNames.Operator
| FSharpTokenColorKind.Punctuation -> ClassificationTypeNames.Punctuation
| FSharpTokenColorKind.Default | _ -> ClassificationTypeNames.Text
let private scanSourceLine(sourceTokenizer: FSharpSourceTokenizer, textLine: TextLine, lineContents: string, lexState: FSharpTokenizerLexState) : SourceLineData =
let colorMap = Array.create textLine.Span.Length ClassificationTypeNames.Text
let lineTokenizer = sourceTokenizer.CreateLineTokenizer(lineContents)
let tokens = ResizeArray()
let private scanSourceLine(sourceTokenizer: FSharpSourceTokenizer, textLine: TextLine, lineContents: string, lexState: FSharpTokenizerLexState) : SourceLineData =
let colorMap = Array.create textLine.Span.Length ClassificationTypeNames.Text
let lineTokenizer = sourceTokenizer.CreateLineTokenizer(lineContents)
let tokens = ResizeArray()
let scanAndColorNextToken(lineTokenizer: FSharpLineTokenizer, lexState: Ref<FSharpTokenizerLexState>) : Option<FSharpTokenInfo> =
let tokenInfoOption, nextLexState = lineTokenizer.ScanToken(lexState.Value)
lexState.Value <- nextLexState
if tokenInfoOption.IsSome then
let classificationType = compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass)
for i = tokenInfoOption.Value.LeftColumn to tokenInfoOption.Value.RightColumn do
Array.set colorMap i classificationType
tokens.Add tokenInfoOption.Value
tokenInfoOption
let scanAndColorNextToken(lineTokenizer: FSharpLineTokenizer, lexState: Ref<FSharpTokenizerLexState>) : Option<FSharpTokenInfo> =
let tokenInfoOption, nextLexState = lineTokenizer.ScanToken(lexState.Value)
lexState.Value <- nextLexState
if tokenInfoOption.IsSome then
let classificationType = compilerTokenToRoslynToken(tokenInfoOption.Value.ColorClass)
for i = tokenInfoOption.Value.LeftColumn to tokenInfoOption.Value.RightColumn do
Array.set colorMap i classificationType
tokens.Add tokenInfoOption.Value
tokenInfoOption
let previousLexState = ref lexState
let mutable tokenInfoOption = scanAndColorNextToken(lineTokenizer, previousLexState)
while tokenInfoOption.IsSome do
tokenInfoOption <- scanAndColorNextToken(lineTokenizer, previousLexState)
let previousLexState = ref lexState
let mutable tokenInfoOption = scanAndColorNextToken(lineTokenizer, previousLexState)
while tokenInfoOption.IsSome do
tokenInfoOption <- scanAndColorNextToken(lineTokenizer, previousLexState)
let mutable startPosition = 0
let mutable endPosition = startPosition
let classifiedSpans = new List<ClassifiedSpan>()
let mutable startPosition = 0
let mutable endPosition = startPosition
let classifiedSpans = new List<ClassifiedSpan>()
while startPosition < colorMap.Length do
let classificationType = colorMap.[startPosition]
endPosition <- startPosition
while endPosition < colorMap.Length && classificationType = colorMap.[endPosition] do
endPosition <- endPosition + 1
let textSpan = new TextSpan(textLine.Start + startPosition, endPosition - startPosition)
classifiedSpans.Add(new ClassifiedSpan(classificationType, textSpan))
startPosition <- endPosition
while startPosition < colorMap.Length do
let classificationType = colorMap.[startPosition]
endPosition <- startPosition
while endPosition < colorMap.Length && classificationType = colorMap.[endPosition] do
endPosition <- endPosition + 1
let textSpan = new TextSpan(textLine.Start + startPosition, endPosition - startPosition)
classifiedSpans.Add(new ClassifiedSpan(classificationType, textSpan))
startPosition <- endPosition
SourceLineData(textLine.Start, lexState, previousLexState.Value, lineContents.GetHashCode(), classifiedSpans, List.ofSeq tokens)
SourceLineData(textLine.Start, lexState, previousLexState.Value, lineContents.GetHashCode(), classifiedSpans, List.ofSeq tokens)
let getColorizationData(documentKey: DocumentId, sourceText: SourceText, textSpan: TextSpan, fileName: string option, defines: string list,
cancellationToken: CancellationToken) : List<ClassifiedSpan> =
try
let sourceTokenizer = FSharpSourceTokenizer(defines, fileName)
let lines = sourceText.Lines
// We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines)
let sourceTextData = dataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count))
let getColorizationData(documentKey: DocumentId, sourceText: SourceText, textSpan: TextSpan, fileName: string option, defines: string list,
cancellationToken: CancellationToken) : List<ClassifiedSpan> =
try
let sourceTokenizer = FSharpSourceTokenizer(defines, fileName)
let lines = sourceText.Lines
// We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines)
let sourceTextData = dataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count))
let startLine = lines.GetLineFromPosition(textSpan.Start).LineNumber
let endLine = lines.GetLineFromPosition(textSpan.End).LineNumber
// Go backwards to find the last cached scanned line that is valid
let scanStartLine =
let mutable i = startLine
while i > 0 && (match sourceTextData.[i] with Some data -> not (data.IsValid(lines.[i])) | None -> true) do
i <- i - 1
i
// Rescan the lines if necessary and report the information
let result = new List<ClassifiedSpan>()
let mutable lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine
let startLine = lines.GetLineFromPosition(textSpan.Start).LineNumber
let endLine = lines.GetLineFromPosition(textSpan.End).LineNumber
// Go backwards to find the last cached scanned line that is valid
let scanStartLine =
let mutable i = startLine
while i > 0 && (match sourceTextData.[i] with Some data -> not (data.IsValid(lines.[i])) | None -> true) do
i <- i - 1
i
// Rescan the lines if necessary and report the information
let result = new List<ClassifiedSpan>()
let mutable lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine
for i = scanStartLine to endLine do
cancellationToken.ThrowIfCancellationRequested()
let textLine = lines.[i]
let lineContents = textLine.Text.ToString(textLine.Span)
for i = scanStartLine to endLine do
cancellationToken.ThrowIfCancellationRequested()
let textLine = lines.[i]
let lineContents = textLine.Text.ToString(textLine.Span)
let lineData =
// We can reuse the old data when
// 1. the line starts at the same overall position
// 2. the hash codes match
// 3. the start-of-line lex states are the same
match sourceTextData.[i] with
| Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState ->
data
| _ ->
// Otherwise, we recompute
let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState)
sourceTextData.[i] <- Some newData
newData
let lineData =
// We can reuse the old data when
// 1. the line starts at the same overall position
// 2. the hash codes match
// 3. the start-of-line lex states are the same
match sourceTextData.[i] with
| Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState ->
data
| _ ->
// Otherwise, we recompute
let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState)
sourceTextData.[i] <- Some newData
newData
lexState <- lineData.LexStateAtEndOfLine
lexState <- lineData.LexStateAtEndOfLine
if startLine <= i then
result.AddRange(lineData.ClassifiedSpans |> Seq.filter(fun token ->
textSpan.Contains(token.TextSpan.Start) ||
textSpan.Contains(token.TextSpan.End - 1) ||
(token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End)))
if startLine <= i then
result.AddRange(lineData.ClassifiedSpans |> Seq.filter(fun token ->
textSpan.Contains(token.TextSpan.Start) ||
textSpan.Contains(token.TextSpan.End - 1) ||
(token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End)))
// If necessary, invalidate all subsequent lines after endLine
if endLine < lines.Count - 1 then
match sourceTextData.[endLine+1] with
| Some data ->
if data.LexStateAtStartOfLine <> lexState then
sourceTextData.ClearFrom (endLine+1)
| None -> ()
result
with
| :? System.OperationCanceledException -> reraise()
| ex ->
Assert.Exception(ex)
List<ClassifiedSpan>()
// If necessary, invalidate all subsequent lines after endLine
if endLine < lines.Count - 1 then
match sourceTextData.[endLine+1] with
| Some data ->
if data.LexStateAtStartOfLine <> lexState then
sourceTextData.ClearFrom (endLine+1)
| None -> ()
result
with
| :? System.OperationCanceledException -> reraise()
| ex ->
Assert.Exception(ex)
List<ClassifiedSpan>()
type private DraftToken = {
Kind: LexerSymbolKind
Token: FSharpTokenInfo
RightColumn: int
} with
static member inline Create kind token =
{ Kind = kind; Token = token; RightColumn = token.LeftColumn + token.FullMatchedLength - 1 }
type private DraftToken = {
Kind: LexerSymbolKind
Token: FSharpTokenInfo
RightColumn: int
} with
static member inline Create kind token =
{ Kind = kind; Token = token; RightColumn = token.LeftColumn + token.FullMatchedLength - 1 }
/// Returns symbol at a given position.
let private getSymbolFromTokens (fileName: string, tokens: FSharpTokenInfo list, linePos: LinePosition, lineStr: string, lookupKind: SymbolLookupKind) : LexerSymbol option =
let isIdentifier t = t.CharClass = FSharpTokenCharKind.Identifier
let isOperator t = t.ColorClass = FSharpTokenColorKind.Operator
let isPunctuation t = t.ColorClass = FSharpTokenColorKind.Punctuation
/// Returns symbol at a given position.
let private getSymbolFromTokens (fileName: string, tokens: FSharpTokenInfo list, linePos: LinePosition, lineStr: string, lookupKind: SymbolLookupKind) : LexerSymbol option =
let isIdentifier t = t.CharClass = FSharpTokenCharKind.Identifier
let isOperator t = t.ColorClass = FSharpTokenColorKind.Operator
let isPunctuation t = t.ColorClass = FSharpTokenColorKind.Punctuation
let inline (|GenericTypeParameterPrefix|StaticallyResolvedTypeParameterPrefix|Other|) (token: FSharpTokenInfo) =
if token.Tag = FSharpTokenTag.QUOTE then GenericTypeParameterPrefix
elif token.Tag = FSharpTokenTag.INFIX_AT_HAT_OP then
// The lexer return INFIX_AT_HAT_OP token for both "^" and "@" symbols.
// We have to check the char itself to distinguish one from another.
if token.FullMatchedLength = 1 && lineStr.[token.LeftColumn] = '^' then
StaticallyResolvedTypeParameterPrefix
else Other
else Other
let inline (|GenericTypeParameterPrefix|StaticallyResolvedTypeParameterPrefix|Other|) (token: FSharpTokenInfo) =
if token.Tag = FSharpTokenTag.QUOTE then GenericTypeParameterPrefix
elif token.Tag = FSharpTokenTag.INFIX_AT_HAT_OP then
// The lexer return INFIX_AT_HAT_OP token for both "^" and "@" symbols.
// We have to check the char itself to distinguish one from another.
if token.FullMatchedLength = 1 && lineStr.[token.LeftColumn] = '^' then
StaticallyResolvedTypeParameterPrefix
else Other
else Other
// Operators: Filter out overlapped operators (>>= operator is tokenized as three distinct tokens: GREATER, GREATER, EQUALS.
// Each of them has FullMatchedLength = 3. So, we take the first GREATER and skip the other two).
//
// Generic type parameters: we convert QUOTE + IDENT tokens into single IDENT token, altering its LeftColumn
// and FullMathedLength (for "'type" which is tokenized as (QUOTE, left=2) + (IDENT, left=3, length=4)
// we'll get (IDENT, left=2, length=5).
//
// Statically resolved type parameters: we convert INFIX_AT_HAT_OP + IDENT tokens into single IDENT token, altering its LeftColumn
// and FullMathedLength (for "^type" which is tokenized as (INFIX_AT_HAT_OP, left=2) + (IDENT, left=3, length=4)
// we'll get (IDENT, left=2, length=5).
let tokens =
let tokensCount = tokens.Length
tokens
|> List.foldi (fun (acc, lastToken) index (token: FSharpTokenInfo) ->
match lastToken with
| Some t when token.LeftColumn <= t.RightColumn -> acc, lastToken
| _ ->
let isLastToken = index = tokensCount - 1
match token with
| GenericTypeParameterPrefix when not isLastToken -> acc, Some (DraftToken.Create LexerSymbolKind.GenericTypeParameter token)
| StaticallyResolvedTypeParameterPrefix when not isLastToken -> acc, Some (DraftToken.Create LexerSymbolKind.StaticallyResolvedTypeParameter token)
// Operators: Filter out overlapped operators (>>= operator is tokenized as three distinct tokens: GREATER, GREATER, EQUALS.
// Each of them has FullMatchedLength = 3. So, we take the first GREATER and skip the other two).
//
// Generic type parameters: we convert QUOTE + IDENT tokens into single IDENT token, altering its LeftColumn
// and FullMathedLength (for "'type" which is tokenized as (QUOTE, left=2) + (IDENT, left=3, length=4)
// we'll get (IDENT, left=2, length=5).
//
// Statically resolved type parameters: we convert INFIX_AT_HAT_OP + IDENT tokens into single IDENT token, altering its LeftColumn
// and FullMathedLength (for "^type" which is tokenized as (INFIX_AT_HAT_OP, left=2) + (IDENT, left=3, length=4)
// we'll get (IDENT, left=2, length=5).
let tokens =
let tokensCount = tokens.Length
tokens
|> List.foldi (fun (acc, lastToken) index (token: FSharpTokenInfo) ->
match lastToken with
| Some t when token.LeftColumn <= t.RightColumn -> acc, lastToken
| _ ->
let draftToken =
match lastToken with
| Some { Kind = LexerSymbolKind.GenericTypeParameter | LexerSymbolKind.StaticallyResolvedTypeParameter as kind } when isIdentifier token ->
DraftToken.Create kind { token with LeftColumn = token.LeftColumn - 1
FullMatchedLength = token.FullMatchedLength + 1 }
// ^ operator
| Some { Kind = LexerSymbolKind.StaticallyResolvedTypeParameter } ->
DraftToken.Create LexerSymbolKind.Operator { token with LeftColumn = token.LeftColumn - 1
FullMatchedLength = 1 }
| _ ->
let kind =
if isOperator token then LexerSymbolKind.Operator
elif isIdentifier token then LexerSymbolKind.Ident
elif isPunctuation token then LexerSymbolKind.Punctuation
else LexerSymbolKind.Other
let isLastToken = index = tokensCount - 1
match token with
| GenericTypeParameterPrefix when not isLastToken -> acc, Some (DraftToken.Create LexerSymbolKind.GenericTypeParameter token)
| StaticallyResolvedTypeParameterPrefix when not isLastToken -> acc, Some (DraftToken.Create LexerSymbolKind.StaticallyResolvedTypeParameter token)
| _ ->
let draftToken =
match lastToken with
| Some { Kind = LexerSymbolKind.GenericTypeParameter | LexerSymbolKind.StaticallyResolvedTypeParameter as kind } when isIdentifier token ->
DraftToken.Create kind { token with LeftColumn = token.LeftColumn - 1
FullMatchedLength = token.FullMatchedLength + 1 }
// ^ operator
| Some { Kind = LexerSymbolKind.StaticallyResolvedTypeParameter } ->
DraftToken.Create LexerSymbolKind.Operator { token with LeftColumn = token.LeftColumn - 1
FullMatchedLength = 1 }
| _ ->
let kind =
if isOperator token then LexerSymbolKind.Operator
elif isIdentifier token then LexerSymbolKind.Ident
elif isPunctuation token then LexerSymbolKind.Punctuation
else LexerSymbolKind.Other
DraftToken.Create kind token
draftToken :: acc, Some draftToken
) ([], None)
|> fst
DraftToken.Create kind token
draftToken :: acc, Some draftToken
) ([], None)
|> fst
// One or two tokens that in touch with the cursor (for "let x|(g) = ()" the tokens will be "x" and "(")
let tokensUnderCursor =
let rightColumnCorrection =
match lookupKind with
| SymbolLookupKind.Precise -> 0
| SymbolLookupKind.Greedy -> 1
// One or two tokens that in touch with the cursor (for "let x|(g) = ()" the tokens will be "x" and "(")
let tokensUnderCursor =
let rightColumnCorrection =
match lookupKind with
| SymbolLookupKind.Precise -> 0
| SymbolLookupKind.Greedy -> 1
tokens |> List.filter (fun x -> x.Token.LeftColumn <= linePos.Character && (x.RightColumn + rightColumnCorrection) >= linePos.Character)
tokens |> List.filter (fun x -> x.Token.LeftColumn <= linePos.Character && (x.RightColumn + rightColumnCorrection) >= linePos.Character)
// Select IDENT token. If failed, select OPERATOR token.
tokensUnderCursor
|> List.tryFind (fun { DraftToken.Kind = k } ->
match k with
| LexerSymbolKind.Ident
| LexerSymbolKind.GenericTypeParameter
| LexerSymbolKind.StaticallyResolvedTypeParameter -> true
| _ -> false)
|> Option.orElseWith (fun _ -> tokensUnderCursor |> List.tryFind (fun { DraftToken.Kind = k } -> k = LexerSymbolKind.Operator))
|> Option.map (fun token ->
let plid, _ = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn)
let identStr = lineStr.Substring(token.Token.LeftColumn, token.Token.FullMatchedLength)
{ Kind = token.Kind
Ident =
Ident(identStr,
Range.mkRange
fileName
(Range.mkPos (linePos.Line + 1) token.Token.LeftColumn)
(Range.mkPos (linePos.Line + 1) (token.RightColumn + 1)))
FullIsland = plid @ [identStr] })
// Select IDENT token. If failed, select OPERATOR token.
tokensUnderCursor
|> List.tryFind (fun { DraftToken.Kind = k } ->
match k with
| LexerSymbolKind.Ident
| LexerSymbolKind.GenericTypeParameter
| LexerSymbolKind.StaticallyResolvedTypeParameter -> true
| _ -> false)
|> Option.orElseWith (fun _ -> tokensUnderCursor |> List.tryFind (fun { DraftToken.Kind = k } -> k = LexerSymbolKind.Operator))
|> Option.map (fun token ->
let plid, _ = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn)
let identStr = lineStr.Substring(token.Token.LeftColumn, token.Token.FullMatchedLength)
{ Kind = token.Kind
Ident =
Ident(identStr,
Range.mkRange
fileName
(Range.mkPos (linePos.Line + 1) token.Token.LeftColumn)
(Range.mkPos (linePos.Line + 1) (token.RightColumn + 1)))
FullIsland = plid @ [identStr] })
let private getCachedSourceLineData(documentKey: DocumentId, sourceText: SourceText, position: int, fileName: string, defines: string list) =
let textLine = sourceText.Lines.GetLineFromPosition(position)
let textLinePos = sourceText.Lines.GetLinePosition(position)
let lineNumber = textLinePos.Line + 1 // FCS line number
let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName)
let lines = sourceText.Lines
// We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines)
let sourceTextData = dataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count))
// Go backwards to find the last cached scanned line that is valid
let scanStartLine =
let mutable i = min (lines.Count - 1) lineNumber
while i > 0 &&
(match sourceTextData.[i] with
| Some data -> not (data.IsValid(lines.[i]))
| None -> true
) do
i <- i - 1
i
let lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine
let lineContents = textLine.Text.ToString(textLine.Span)
let private getCachedSourceLineData(documentKey: DocumentId, sourceText: SourceText, position: int, fileName: string, defines: string list) =
let textLine = sourceText.Lines.GetLineFromPosition(position)
let textLinePos = sourceText.Lines.GetLinePosition(position)
let lineNumber = textLinePos.Line + 1 // FCS line number
let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName)
let lines = sourceText.Lines
// We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines)
let sourceTextData = dataCache.GetValue(documentKey, fun key -> SourceTextData(lines.Count))
// Go backwards to find the last cached scanned line that is valid
let scanStartLine =
let mutable i = min (lines.Count - 1) lineNumber
while i > 0 &&
(match sourceTextData.[i] with
| Some data -> not (data.IsValid(lines.[i]))
| None -> true
) do
i <- i - 1
i
let lexState = if scanStartLine = 0 then 0L else sourceTextData.[scanStartLine - 1].Value.LexStateAtEndOfLine
let lineContents = textLine.Text.ToString(textLine.Span)
// We can reuse the old data when
// 1. the line starts at the same overall position
// 2. the hash codes match
// 3. the start-of-line lex states are the same
match sourceTextData.[lineNumber] with
| Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState ->
data, textLinePos, lineContents
| _ ->
// Otherwise, we recompute
let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState)
sourceTextData.[lineNumber] <- Some newData
newData, textLinePos, lineContents
// We can reuse the old data when
// 1. the line starts at the same overall position
// 2. the hash codes match
// 3. the start-of-line lex states are the same
match sourceTextData.[lineNumber] with
| Some data when data.IsValid(textLine) && data.LexStateAtStartOfLine = lexState ->
data, textLinePos, lineContents
| _ ->
// Otherwise, we recompute
let newData = scanSourceLine(sourceTokenizer, textLine, lineContents, lexState)
sourceTextData.[lineNumber] <- Some newData
newData, textLinePos, lineContents
let tokenizeLine (documentKey, sourceText, position, fileName, defines) =
try
let lineData, _, _ = getCachedSourceLineData(documentKey, sourceText, position, fileName, defines)
lineData.Tokens
with
| ex ->
Assert.Exception(ex)
[]
let tokenizeLine (documentKey, sourceText, position, fileName, defines) =
try
let lineData, _, _ = getCachedSourceLineData(documentKey, sourceText, position, fileName, defines)
lineData.Tokens
with
| ex ->
Assert.Exception(ex)
[]
let getSymbolAtPosition(documentKey: DocumentId, sourceText: SourceText, position: int, fileName: string, defines: string list, lookupKind: SymbolLookupKind) : LexerSymbol option =
try
let lineData, textLinePos, lineContents = getCachedSourceLineData(documentKey, sourceText, position, fileName, defines)
getSymbolFromTokens(fileName, lineData.Tokens, textLinePos, lineContents, lookupKind)
with
| :? System.OperationCanceledException -> reraise()
| ex ->
Assert.Exception(ex)
None
let getSymbolAtPosition(documentKey: DocumentId, sourceText: SourceText, position: int, fileName: string, defines: string list, lookupKind: SymbolLookupKind) : LexerSymbol option =
try
let lineData, textLinePos, lineContents = getCachedSourceLineData(documentKey, sourceText, position, fileName, defines)
getSymbolFromTokens(fileName, lineData.Tokens, textLinePos, lineContents, lookupKind)
with
| :? System.OperationCanceledException -> reraise()
| ex ->
Assert.Exception(ex)
None
/// Fix invalid span if it appears to have redundant suffix and prefix.
let fixupSpan (sourceText: SourceText, span: TextSpan) : TextSpan =
let text = sourceText.GetSubText(span).ToString()
match text.LastIndexOf '.' with
| -1 | 0 -> span
| index -> TextSpan(span.Start + index + 1, text.Length - index - 1)
/// Fix invalid span if it appears to have redundant suffix and prefix.
let fixupSpan (sourceText: SourceText, span: TextSpan) : TextSpan =
let text = sourceText.GetSubText(span).ToString()
match text.LastIndexOf '.' with
| -1 | 0 -> span
| index -> TextSpan(span.Start + index + 1, text.Length - index - 1)
let isValidNameForSymbol (lexerSymbolKind: LexerSymbolKind, symbol: FSharpSymbol, name: string) : bool =
let doubleBackTickDelimiter = "``"
let isValidNameForSymbol (lexerSymbolKind: LexerSymbolKind, symbol: FSharpSymbol, name: string) : bool =
let doubleBackTickDelimiter = "``"
let isDoubleBacktickIdent (s: string) =
let doubledDelimiter = 2 * doubleBackTickDelimiter.Length
if s.StartsWith(doubleBackTickDelimiter) && s.EndsWith(doubleBackTickDelimiter) && s.Length > doubledDelimiter then
let inner = s.Substring(doubleBackTickDelimiter.Length, s.Length - doubledDelimiter)
not (inner.Contains(doubleBackTickDelimiter))
else false
let isDoubleBacktickIdent (s: string) =
let doubledDelimiter = 2 * doubleBackTickDelimiter.Length
if s.StartsWith(doubleBackTickDelimiter) && s.EndsWith(doubleBackTickDelimiter) && s.Length > doubledDelimiter then
let inner = s.Substring(doubleBackTickDelimiter.Length, s.Length - doubledDelimiter)
not (inner.Contains(doubleBackTickDelimiter))
else false
let isIdentifier (ident: string) =
if isDoubleBacktickIdent ident then
true
else
ident
|> Seq.mapi (fun i c -> i, c)
|> Seq.forall (fun (i, c) ->
if i = 0 then PrettyNaming.IsIdentifierFirstCharacter c
else PrettyNaming.IsIdentifierPartCharacter c)
let isIdentifier (ident: string) =
if isDoubleBacktickIdent ident then
true
else
ident
|> Seq.mapi (fun i c -> i, c)
|> Seq.forall (fun (i, c) ->
if i = 0 then PrettyNaming.IsIdentifierFirstCharacter c
else PrettyNaming.IsIdentifierPartCharacter c)
let isFixableIdentifier (s: string) =
not (String.IsNullOrEmpty s) && Lexhelp.Keywords.NormalizeIdentifierBackticks s |> isIdentifier
let isFixableIdentifier (s: string) =
not (String.IsNullOrEmpty s) && Lexhelp.Keywords.NormalizeIdentifierBackticks s |> isIdentifier
let forbiddenChars = [| '.'; '+'; '$'; '&'; '['; ']'; '/'; '\\'; '*'; '\'' |]
let forbiddenChars = [| '.'; '+'; '$'; '&'; '['; ']'; '/'; '\\'; '*'; '\'' |]
let isTypeNameIdent (s: string) =
not (String.IsNullOrEmpty s) && s.IndexOfAny forbiddenChars = -1 && isFixableIdentifier s
let isTypeNameIdent (s: string) =
not (String.IsNullOrEmpty s) && s.IndexOfAny forbiddenChars = -1 && isFixableIdentifier s
let isUnionCaseIdent (s: string) =
isTypeNameIdent s && Char.IsUpper(s.Replace(doubleBackTickDelimiter, "").[0])
let isUnionCaseIdent (s: string) =
isTypeNameIdent s && Char.IsUpper(s.Replace(doubleBackTickDelimiter, "").[0])
let isTypeParameter (prefix: char) (s: string) =
s.Length >= 2 && s.[0] = prefix && isIdentifier s.[1..]
let isTypeParameter (prefix: char) (s: string) =
s.Length >= 2 && s.[0] = prefix && isIdentifier s.[1..]
let isGenericTypeParameter = isTypeParameter '''
let isStaticallyResolvedTypeParameter = isTypeParameter '^'
let isGenericTypeParameter = isTypeParameter '''
let isStaticallyResolvedTypeParameter = isTypeParameter '^'
match lexerSymbolKind, symbol with
| _, :? FSharpUnionCase -> isUnionCaseIdent name
| _, :? FSharpActivePatternCase ->
// Different from union cases, active patterns don't accept double-backtick identifiers
isFixableIdentifier name && not (String.IsNullOrEmpty name) && Char.IsUpper(name.[0])
| LexerSymbolKind.Operator, _ -> PrettyNaming.IsOperatorName name
| LexerSymbolKind.Punctuation, _ -> PrettyNaming.IsPunctuation name
| LexerSymbolKind.GenericTypeParameter, _ -> isGenericTypeParameter name
| LexerSymbolKind.StaticallyResolvedTypeParameter, _ -> isStaticallyResolvedTypeParameter name
| (LexerSymbolKind.Ident | LexerSymbolKind.Other), _ ->
match symbol with
| :? FSharpEntity as e when e.IsClass || e.IsFSharpRecord || e.IsFSharpUnion || e.IsValueType || e.IsFSharpModule || e.IsInterface -> isTypeNameIdent name
| _ -> isFixableIdentifier name
match lexerSymbolKind, symbol with
| _, :? FSharpUnionCase -> isUnionCaseIdent name
| _, :? FSharpActivePatternCase ->
// Different from union cases, active patterns don't accept double-backtick identifiers
isFixableIdentifier name && not (String.IsNullOrEmpty name) && Char.IsUpper(name.[0])
| LexerSymbolKind.Operator, _ -> PrettyNaming.IsOperatorName name
| LexerSymbolKind.Punctuation, _ -> PrettyNaming.IsPunctuation name
| LexerSymbolKind.GenericTypeParameter, _ -> isGenericTypeParameter name
| LexerSymbolKind.StaticallyResolvedTypeParameter, _ -> isStaticallyResolvedTypeParameter name
| (LexerSymbolKind.Ident | LexerSymbolKind.Other), _ ->
match symbol with
| :? FSharpEntity as e when e.IsClass || e.IsFSharpRecord || e.IsFSharpUnion || e.IsValueType || e.IsFSharpModule || e.IsInterface -> isTypeNameIdent name
| _ -> isFixableIdentifier name
module internal Microsoft.VisualStudio.FSharp.Editor.TypedAstUtils
open System
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.VisualStudio.FSharp.Editor
open System.Text.RegularExpressions
let isAttribute<'T> (attribute: FSharpAttribute) =
......@@ -43,7 +41,6 @@ let private UnnamedUnionFieldRegex = Regex("^Item(\d+)?$", RegexOptions.Compiled
let isUnnamedUnionCaseField (field: FSharpField) = UnnamedUnionFieldRegex.IsMatch(field.Name)
module TypedAstPatterns =
let (|AbbreviatedType|_|) (entity: FSharpEntity) =
......
......@@ -54,7 +54,7 @@ type internal FSharpFindUsagesService
let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> Seq.toList)
let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy)
let! symbolUse = checkFileResults.GetSymbolUseAtLocation(lineNumber, symbol.Ident.idRange.EndColumn, textLine, symbol.FullIsland)
let! declaration = checkFileResults.GetDeclarationLocationAlternate (lineNumber, symbol.Ident.idRange.EndColumn, textLine, symbol.FullIsland, false) |> liftAsync
let tags = GlyphTags.GetTags(Tokenizer.GetGlyphForSymbol (symbolUse.Symbol, symbol.Kind))
......
......@@ -45,7 +45,7 @@ module internal FSharpGoToDefinition =
let textLine = sourceText.Lines.GetLineFromPosition position
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let! lexerSymbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! lexerSymbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, SymbolLookupKind.Greedy)
let! _, _, checkFileResults =
checker.ParseAndCheckDocument
(filePath, textVersionHash, sourceText.ToString(), options, allowStaleResults = preferSignature)
......@@ -82,22 +82,17 @@ module internal FSharpGoToDefinition =
asyncMaybe {
let! projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument
let defines = CompilerEnvironment.GetCompilationDefinesForEditing (originDocument.FilePath, projectOptions.OtherOptions |> Seq.toList)
let originTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sourceText, originRange)
let position = originTextSpan.Start
let! lexerSymbol =
Tokenizer.getSymbolAtPosition
(originDocument.Id, sourceText, position, originDocument.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! lexerSymbol = Tokenizer.getSymbolAtPosition(originDocument.Id, sourceText, position, originDocument.FilePath, defines, SymbolLookupKind.Greedy)
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
let! _, _, checkFileResults =
checker.ParseAndCheckDocument (originDocument,projectOptions,allowStaleResults=true,sourceText=sourceText)
let idRange = lexerSymbol.Ident.idRange
let! fsSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
let symbol = fsSymbolUse.Symbol
// if the tooltip was spawned in an implementation file and we have a range targeting
......@@ -109,10 +104,7 @@ module internal FSharpGoToDefinition =
let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath
let! implSourceText = implDoc.GetTextAsync ()
let! projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject implDoc
let! _, _, checkFileResults =
checker.ParseAndCheckDocument (implDoc, projectOptions, allowStaleResults=true, sourceText=implSourceText)
let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, allowStaleResults=true, sourceText=implSourceText)
let! symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol |> liftAsync
let! implSymbol = symbolUses |> Array.tryHead
let implTextSpan = RoslynHelpers.FSharpRangeToTextSpan (implSourceText, implSymbol.RangeAlternate)
......@@ -127,12 +119,10 @@ module internal FSharpGoToDefinition =
(targetDocument:Document, symbolRange:range, targetSource:SourceText, checker: FSharpChecker, projectInfoManager: ProjectInfoManager) =
findSymbolHelper (targetDocument, symbolRange, targetSource,true, checker, projectInfoManager)
/// find the definition location (implementation file/.fs) of the target symbol
let findDefinitionOfSymbolAtRange
(targetDocument:Document, symbolRange:range, targetSourceText:SourceText, checker: FSharpChecker, projectInfoManager: ProjectInfoManager) =
findSymbolHelper (targetDocument, symbolRange, targetSourceText,false, checker, projectInfoManager)
/// use the targetSymbol to find the first instance of its presence in the provided source file
let findSymbolDeclarationInFile
......@@ -148,7 +138,6 @@ module internal FSharpGoToDefinition =
return implSymbol.RangeAlternate
}
open FSharpGoToDefinition
[<Shared>]
......@@ -213,7 +202,6 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
clearStatusBarAfter 4000
true
/// Navigate to the positon of the textSpan in the provided document
// used by quickinfo link navigation when the tooltip contains the correct destination range
member this.TryNavigateToTextSpan (document:Document, textSpan:TextSpan) =
......@@ -227,7 +215,6 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
clearStatusBarAfter 4000
false
/// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
member this.NavigateToSymbolDeclarationAsync (targetDocument:Document, targetSourceText:SourceText, symbolRange:range) = async {
......@@ -236,7 +223,6 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
(targetDocument, symbolRange, targetSourceText, checkerProvider.Checker, projectInfoManager)
return tryNavigateToItem navresult
}
/// find the definition location (implementation file/.fs) of the target symbol
member this.NavigateToSymbolDefinitionAsync (targetDocument:Document, targetSourceText:SourceText, symbolRange:range)= async{
......@@ -246,14 +232,13 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
return tryNavigateToItem navresult
}
static member FindDefinition
(checker: FSharpChecker, documentKey: DocumentId, sourceText: SourceText, filePath: string, position: int,
defines: string list, options: FSharpProjectOptions, textVersionHash: int) : Option<range> = maybe {
let textLine = sourceText.Lines.GetLineFromPosition position
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let! lexerSymbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, Tokenizer.SymbolLookupKind.Greedy)
let! lexerSymbol = Tokenizer.getSymbolAtPosition(documentKey, sourceText, position, filePath, defines, SymbolLookupKind.Greedy)
let! _, _, checkFileResults =
checker.ParseAndCheckDocument
(filePath, textVersionHash, sourceText.ToString(), options, allowStaleResults = true) |> Async.RunSynchronously
......@@ -267,7 +252,6 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
| _ -> return! None
}
/// Construct a task that will return a navigation target for the implementation definition of the symbol
/// at the provided position in the document
member this.FindDefinitionsTask (originDocument:Document, position:int, cancellationToken:CancellationToken) =
......@@ -288,7 +272,7 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
let! lexerSymbol =
Tokenizer.getSymbolAtPosition
(originDocument.Id, sourceText, position,originDocument.FilePath, defines, Tokenizer.SymbolLookupKind.Greedy)
(originDocument.Id, sourceText, position,originDocument.FilePath, defines, SymbolLookupKind.Greedy)
let idRange = lexerSymbol.Ident.idRange
let! declarations =
......@@ -362,13 +346,10 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
} |> Async.map (Option.defaultValue Seq.empty)
|> RoslynHelpers.StartAsyncAsTask cancellationToken
interface IGoToDefinitionService with
// used for 'definition peek'
member this.FindDefinitionsAsync (document: Document, position: int, cancellationToken: CancellationToken) =
this.FindDefinitionsTask (document, position, cancellationToken)
// used for 'goto definition' proper
/// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument
......@@ -413,4 +394,4 @@ type internal FSharpGoToDefinitionService [<ImportingConstructor>]
stopSearchAnimation ()
statusBarMessage "Could Not Navigate to Definition of Symbol Under Caret"
clearStatusBarAfter 4000
true // we always return true to prevent the dialog box from appearing
true // we always return true to prevent the dialog box from appearing
\ No newline at end of file
......@@ -10,6 +10,7 @@ open System.Windows.Controls
open System.Windows.Data
open System.Windows.Media
open System.ComponentModel.Composition
open System.Text
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Classification
......@@ -29,11 +30,8 @@ open Microsoft.VisualStudio.Language.Intellisense
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler
open Internal.Utilities.StructuredFormat
open Tokenizer
open System.Text
open RoslynHelpers
open Internal.Utilities.StructuredFormat
module private SessionHandling =
let mutable currentSession = None
......@@ -53,19 +51,17 @@ type internal SourceLink(run) as this =
inherit Documents.Hyperlink(run)
let lessOpacity =
{ new IValueConverter with
member this.Convert(value, targetType, _, _) =
match value with
| :? Color as c when targetType = typeof<Color> ->
// return same color but slightly transparent
Color.FromArgb(70uy, c.R, c.G, c.B) :> _
| _ -> DependencyProperty.UnsetValue
member this.ConvertBack(_,_,_,_) = DependencyProperty.UnsetValue }
{ new IValueConverter with
member this.Convert(value, targetType, _, _) =
match value with
| :? Color as c when targetType = typeof<Color> ->
// return same color but slightly transparent
Color.FromArgb(70uy, c.R, c.G, c.B) :> _
| _ -> DependencyProperty.UnsetValue
member this.ConvertBack(_,_,_,_) = DependencyProperty.UnsetValue }
let underlineBrush = Media.SolidColorBrush()
do BindingOperations.SetBinding(underlineBrush, SolidColorBrush.ColorProperty, Binding("Foreground.Color", Source = this, Converter = lessOpacity)) |> ignore
let normalUnderline = TextDecorationCollection [TextDecoration(Location = TextDecorationLocation.Underline, PenOffset = 1.0)]
let slightUnderline = TextDecorationCollection [TextDecoration(Location = TextDecorationLocation.Underline, PenOffset = 1.0, Pen = Pen(Brush = underlineBrush))]
do this.TextDecorations <- slightUnderline
......@@ -73,66 +69,83 @@ type internal SourceLink(run) as this =
override this.OnMouseEnter(e) =
base.OnMouseEnter(e)
this.TextDecorations <- normalUnderline
override this.OnMouseLeave(e) =
base.OnMouseLeave(e)
this.TextDecorations <- slightUnderline
module private FSharpQuickInfo =
type private TooltipInfo =
{ StructuredText: FSharpStructuredToolTipText
Span: TextSpan
Symbol: FSharpSymbol
SymbolKind: LexerSymbolKind }
let tooltip (symbolGlyph, mainDescription, documentation) =
let empty =
{ new IDeferredQuickInfoContent with
member x.Create() = TextBlock(Visibility = Visibility.Collapsed) :> FrameworkElement }
module private FSharpQuickInfo =
let private empty =
{ new IDeferredQuickInfoContent with
member x.Create() = TextBlock(Visibility = Visibility.Collapsed) :> FrameworkElement }
let createDeferredContent (symbolGlyph, mainDescription, documentation) =
QuickInfoDisplayDeferredContent(symbolGlyph, null, mainDescription, documentation, empty, empty, empty, empty)
/// Get tooltip combined from doccom of Signature and definition
let getCompoundTooltipInfo (checker: FSharpChecker, position: int, document: Document, projectInfoManager: ProjectInfoManager, cancellationToken) =
// when a construct has been declared in a signature file the documentation comments that are
// written in that file are the ones that go into the generated xml when the project is compiled
// therefore we should include these doccoms in our design time tooltips
let getTooltipFromRange
(
checker: FSharpChecker,
projectInfoManager: ProjectInfoManager,
document: Document,
declRange: range,
cancellationToken: CancellationToken
)
: Async<TooltipInfo option> =
asyncMaybe {
let solution = document.Project.Solution
// ascertain the location of the target declaration in the signature file
let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead
let extDocument = solution.GetProject(extDocId.ProjectId).GetDocument extDocId
let! extSourceText = extDocument.GetTextAsync cancellationToken
let extSpan = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, declRange)
let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString()
// when a construct has been declared in a signature file the documentation comments that are
// written in that file are the ones that go into the generated xml when the project is compiled
// therefore we should include these doccoms in our design time tooltips
let getTooltipFromRange (declRange: range) =
asyncMaybe {
// ascertain the location of the target declaration in the signature file
let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead
let extDocument = solution.GetProject(extDocId.ProjectId).GetDocument extDocId
let! extSourceText = extDocument.GetTextAsync cancellationToken
let extSpan = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, declRange)
let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString()
// project options need to be retrieved because the signature file could be in another project
let extProjectOptions = projectInfoManager.TryGetOptionsForProject extDocId.ProjectId |> Option.get
let extDefines =
CompilerEnvironment.GetCompilationDefinesForEditing
(extDocument.FilePath, extProjectOptions.OtherOptions |> Seq.toList)
let! extLexerSymbol =
Tokenizer.getSymbolAtPosition
(extDocId, extSourceText, extSpan.Start, declRange.FileName, extDefines, SymbolLookupKind.Greedy)
let! _, _, extCheckFileResults =
checker.ParseAndCheckDocument (extDocument,extProjectOptions,allowStaleResults=true,sourceText=extSourceText)
let! extTooltipText =
extCheckFileResults.GetStructuredToolTipTextAlternate
(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland, FSharpTokenTag.IDENT) |> liftAsync
match extTooltipText with
| FSharpToolTipText []
| FSharpToolTipText [FSharpStructuredToolTipElement.None] ->
return! None
| extTooltipText ->
let! extSymbolUse =
extCheckFileResults.GetSymbolUseAtLocation(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland)
let extTextSpan = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, extLexerSymbol.Range)
return! Some (extTooltipText, extTextSpan, extSymbolUse, extLexerSymbol.Kind)
}
// project options need to be retrieved because the signature file could be in another project
let! extProjectOptions = projectInfoManager.TryGetOptionsForProject extDocId.ProjectId
let extDefines = CompilerEnvironment.GetCompilationDefinesForEditing (extDocument.FilePath, List.ofSeq extProjectOptions.OtherOptions)
let! extLexerSymbol = Tokenizer.getSymbolAtPosition(extDocId, extSourceText, extSpan.Start, declRange.FileName, extDefines, SymbolLookupKind.Greedy)
let! _, _, extCheckFileResults = checker.ParseAndCheckDocument(extDocument,extProjectOptions,allowStaleResults=true,sourceText=extSourceText)
let! extTooltipText =
extCheckFileResults.GetStructuredToolTipTextAlternate
(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland, FSharpTokenTag.IDENT) |> liftAsync
match extTooltipText with
| FSharpToolTipText []
| FSharpToolTipText [FSharpStructuredToolTipElement.None] -> return! None
| extTooltipText ->
let! extSymbolUse =
extCheckFileResults.GetSymbolUseAtLocation(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland)
return { StructuredText = extTooltipText
Span = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, extLexerSymbol.Range)
Symbol = extSymbolUse.Symbol
SymbolKind = extLexerSymbol.Kind }
}
/// Get tooltip combined from doccom of Signature and definition
let getTooltipInfo
(
checker: FSharpChecker,
projectInfoManager: ProjectInfoManager,
document: Document,
position: int,
cancellationToken: CancellationToken
)
: Async<(FSharpSymbolUse * TooltipInfo option * TooltipInfo option) option> =
asyncMaybe {
let! sourceText = document.GetTextAsync cancellationToken
let! projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject document
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, projectOptions.OtherOptions |> Seq.toList)
......@@ -142,6 +155,7 @@ module private FSharpQuickInfo =
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
let! symbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
/// Gets the tooltip information for the orignal target
let getTargetSymbolTooltip () =
......@@ -154,30 +168,32 @@ module private FSharpQuickInfo =
| FSharpToolTipText []
| FSharpToolTipText [FSharpStructuredToolTipElement.None] -> return! None
| _ ->
let! symbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
let targetTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sourceText, lexerSymbol.Range)
return! Some (targetTooltip, targetTextSpan, symbolUse, lexerSymbol.Kind)
return { StructuredText = targetTooltip
Span = targetTextSpan
Symbol = symbolUse.Symbol
SymbolKind = lexerSymbol.Kind }
}
// if the target is in a signature file, adjusting the tooltip info is unnecessary
if isSignatureFile document.FilePath then
let! targetTooltipInfo = getTargetSymbolTooltip()
return (None ,Some targetTooltipInfo)
return symbolUse, None, Some targetTooltipInfo
else
// find the declaration location of the target symbol, with a preference for signature files
let! findSigDeclarationResult =
checkFileResults.GetDeclarationLocationAlternate
(idRange.StartLine, idRange.EndColumn, lineText, lexerSymbol.FullIsland, preferFlag=true) |> liftAsync
(idRange.StartLine, idRange.EndColumn, lineText, lexerSymbol.FullIsland, preferFlag=true) |> liftAsync
// it is necessary to retrieve the backup tooltip info because this acquires
// the textSpan designating where we want the tooltip to appear.
let! backupTooltipInfo & (_, targetTextSpan, _, _) = getTargetSymbolTooltip()
let! targetTooltipInfo = getTargetSymbolTooltip()
match findSigDeclarationResult with
| FSharpFindDeclResult.DeclNotFound _failReason -> return None, Some backupTooltipInfo
| FSharpFindDeclResult.DeclNotFound _ -> return symbolUse, None, Some targetTooltipInfo
| FSharpFindDeclResult.DeclFound declRange ->
if isSignatureFile declRange.FileName then
let! sigTooltipInfo = getTooltipFromRange declRange
let! sigTooltipInfo = getTooltipFromRange(checker, projectInfoManager, document, declRange, cancellationToken)
// if the target was declared in a signature file, and the current file
// is not the corresponding module implementation file for that signature,
// the doccoms from the signature will overwrite any doccoms that might be
......@@ -188,12 +204,12 @@ module private FSharpQuickInfo =
(idRange.StartLine, idRange.EndColumn, lineText, lexerSymbol.FullIsland, preferFlag=false) |> liftAsync
match findImplDefinitionResult with
| FSharpFindDeclResult.DeclNotFound _failReason -> return (Some sigTooltipInfo , None)
| FSharpFindDeclResult.DeclNotFound _ -> return symbolUse, Some sigTooltipInfo, None
| FSharpFindDeclResult.DeclFound declRange ->
let! (implTooltip, _, implSymbol, implLex) = getTooltipFromRange declRange
return (Some sigTooltipInfo, Some (implTooltip, targetTextSpan, implSymbol, implLex))
let! implTooltipInfo = getTooltipFromRange(checker, projectInfoManager, document, declRange, cancellationToken)
return symbolUse, Some sigTooltipInfo, Some { implTooltipInfo with Span = targetTooltipInfo.Span }
else
return (None, Some backupTooltipInfo)
return symbolUse, None, Some targetTooltipInfo
}
[<ExportQuickInfoProvider(PredefinedQuickInfoProviderNames.Semantic, FSharpConstants.FSharpLanguageName)>]
......@@ -239,34 +255,37 @@ type internal FSharpQuickInfoProvider
// adjust the target from implmentation to signature
| Signature, Implementation ->
return! gotoDefinitionService.NavigateToSymbolDeclarationAsync (targetDoc, targetSource, range)|>liftAsync
} |> Async.map (Option.map (fun res ->
if res then
SessionHandling.currentSession
|> Option.iter (fun session -> session.Dismiss ())
)) |> Async.Ignore |> Async.StartImmediate
}
|> Async.map(
function
| Some true -> SessionHandling.currentSession |> Option.iter (fun session -> session.Dismiss())
| _ -> ())
|> Async.Ignore
|> Async.StartImmediate
let formatMap = typemap.ClassificationFormatMapService.GetClassificationFormatMap "tooltip"
let layoutTagToFormatting (layoutTag: LayoutTag) =
layoutTag
|> roslynTag
|> RoslynHelpers.roslynTag
|> ClassificationTags.GetClassificationTypeName
|> typemap.GetClassificationType
|> formatMap.GetTextProperties
let inlines = seq {
for taggedText in content do
let run = Documents.Run taggedText.Text
let inl =
match taggedText with
| :? Layout.NavigableTaggedText as nav when thisSymbolUseRange <> nav.Range && isTargetValid nav.Range ->
let h = SourceLink (run, ToolTip = nav.Range.FileName)
h.Click.Add <| fun _ -> navigateTo nav.Range
h :> Documents.Inline
| _ -> run :> _
DependencyObjectExtensions.SetTextProperties (inl, layoutTagToFormatting taggedText.Tag)
yield inl
}
let inlines =
seq {
for taggedText in content do
let run = Documents.Run taggedText.Text
let inl =
match taggedText with
| :? Layout.NavigableTaggedText as nav when thisSymbolUseRange <> nav.Range && isTargetValid nav.Range ->
let h = SourceLink (run, ToolTip = nav.Range.FileName)
h.Click.Add (fun _ -> navigateTo nav.Range)
h :> Documents.Inline
| _ -> run :> _
DependencyObjectExtensions.SetTextProperties (inl, layoutTagToFormatting taggedText.Tag)
yield inl
}
let createTextLinks () =
let tb = TextBlock(TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.None)
......@@ -275,11 +294,11 @@ type internal FSharpQuickInfoProvider
if tb.Inlines.Count = 0 then tb.Visibility <- Visibility.Collapsed
tb :> FrameworkElement
{ new IDeferredQuickInfoContent with member x.Create () = createTextLinks () }
{ new IDeferredQuickInfoContent with member x.Create() = createTextLinks() }
let xmlMemberIndexService = serviceProvider.GetService(typeof<SVsXMLMemberIndexService>) :?> IVsXMLMemberIndexService
let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE)
static member ProvideQuickInfo(checker: FSharpChecker, documentId: DocumentId, sourceText: SourceText, filePath: string, position: int, options: FSharpProjectOptions, textVersionHash: int) =
asyncMaybe {
let! _, _, checkFileResults = checker.ParseAndCheckDocument (filePath, textVersionHash, sourceText.ToString(), options, allowStaleResults = true)
......@@ -293,33 +312,33 @@ type internal FSharpQuickInfoProvider
| FSharpToolTipText [FSharpStructuredToolTipElement.None] -> return! None
| _ ->
let! symbolUse = checkFileResults.GetSymbolUseAtLocation (textLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland)
return! Some (res, RoslynHelpers.FSharpRangeToTextSpan (sourceText, symbol.Range), symbolUse.Symbol, symbol.Kind)
return res, RoslynHelpers.FSharpRangeToTextSpan (sourceText, symbol.Range), symbolUse.Symbol, symbol.Kind
}
interface IQuickInfoProvider with
override this.GetItemAsync(document: Document, position: int, cancellationToken: CancellationToken): Task<QuickInfoItem> =
asyncMaybe {
let! sigTooltipInfo, targetTooltipInfo =
FSharpQuickInfo.getCompoundTooltipInfo(checkerProvider.Checker, position, document, projectInfoManager, cancellationToken)
let! symbolUse, sigTooltipInfo, targetTooltipInfo =
FSharpQuickInfo.getTooltipInfo(checkerProvider.Checker, projectInfoManager, document, position, cancellationToken)
match sigTooltipInfo, targetTooltipInfo with
| None, None -> return null
| Some (toolTipElement, textSpan, symbolUse, symbolKind), None
| None, Some (toolTipElement, textSpan, symbolUse, symbolKind) ->
let mainDescription = Collections.Generic.List ()
let documentation = Collections.Generic.List ()
XmlDocumentation.BuildDataTipText(documentationBuilder, mainDescription.Add, documentation.Add, toolTipElement)
| Some tooltip, None
| None, Some tooltip ->
let mainDescription = ResizeArray()
let documentation = ResizeArray()
XmlDocumentation.BuildDataTipText(documentationBuilder, mainDescription.Add, documentation.Add, tooltip.StructuredText)
let content =
FSharpQuickInfo.tooltip
(SymbolGlyphDeferredContent(GetGlyphForSymbol(symbolUse.Symbol, symbolKind), glyphService),
FSharpQuickInfo.createDeferredContent
(SymbolGlyphDeferredContent(Tokenizer.GetGlyphForSymbol(tooltip.Symbol, tooltip.SymbolKind), glyphService),
fragment (mainDescription, typeMap, document, symbolUse.RangeAlternate),
fragment (documentation, typeMap, document, symbolUse.RangeAlternate))
return QuickInfoItem (textSpan, content)
return QuickInfoItem (tooltip.Span, content)
| Some (sigToolTipElement, _, _, _), Some (targetToolTipElement, targetTextSpan, targetSymbolUse, targetSymbolKind) ->
| Some sigTooltip, Some targetTooltip ->
let description, targetDocumentation, sigDocumentation = ResizeArray(), ResizeArray(), ResizeArray()
XmlDocumentation.BuildDataTipText(documentationBuilder, ignore, sigDocumentation.Add, sigToolTipElement)
XmlDocumentation.BuildDataTipText(documentationBuilder, description.Add, targetDocumentation.Add, targetToolTipElement)
XmlDocumentation.BuildDataTipText(documentationBuilder, ignore, sigDocumentation.Add, sigTooltip.StructuredText)
XmlDocumentation.BuildDataTipText(documentationBuilder, description.Add, targetDocumentation.Add, targetTooltip.StructuredText)
let width =
description
......@@ -333,19 +352,21 @@ type internal FSharpQuickInfoProvider
// get whitespace nomalized documentation text
let getText (tts: seq<Layout.TaggedText>) =
((StringBuilder(), tts) ||> Seq.fold (fun sb tt ->
if String.IsNullOrWhiteSpace tt.Text then sb else sb.Append tt.Text)).ToString()
let text =
(StringBuilder(), tts)
||> Seq.fold (fun sb tt ->
if String.IsNullOrWhiteSpace tt.Text then sb else sb.Append tt.Text)
|> string
if String.IsNullOrWhiteSpace text then None else Some text
let documentation =
let implText, sigText = getText targetDocumentation, getText sigDocumentation
let implDocsEmpty, sigDocsEmpty = String.IsNullOrWhiteSpace implText, String.IsNullOrWhiteSpace sigText
[ match implDocsEmpty, sigDocsEmpty with
| true, true -> ()
| true, false -> yield! sigDocumentation
| false, true -> yield! targetDocumentation
| false, false when implText.Equals (sigText, StringComparison.OrdinalIgnoreCase) -> yield! sigDocumentation
| false, false ->
[ match getText targetDocumentation, getText sigDocumentation with
| None, None -> ()
| None, Some _ -> yield! sigDocumentation
| Some _, None -> yield! targetDocumentation
| Some implText, Some sigText when implText.Equals (sigText, StringComparison.OrdinalIgnoreCase) ->
yield! sigDocumentation
| Some _, Some _ ->
yield! sigDocumentation
yield lineBreak
yield seperator
......@@ -353,11 +374,11 @@ type internal FSharpQuickInfoProvider
yield! targetDocumentation ]
let content =
FSharpQuickInfo.tooltip
(SymbolGlyphDeferredContent (GetGlyphForSymbol (targetSymbolUse.Symbol, targetSymbolKind), glyphService),
fragment (description, typeMap, document, targetSymbolUse.RangeAlternate),
fragment (documentation, typeMap, document, targetSymbolUse.RangeAlternate))
FSharpQuickInfo.createDeferredContent
(SymbolGlyphDeferredContent (Tokenizer.GetGlyphForSymbol (targetTooltip.Symbol, targetTooltip.SymbolKind), glyphService),
fragment (description, typeMap, document, symbolUse.RangeAlternate),
fragment (documentation, typeMap, document, symbolUse.RangeAlternate))
return QuickInfoItem (targetTextSpan, content)
return QuickInfoItem (targetTooltip.Span, content)
} |> Async.map Option.toObj
|> RoslynHelpers.StartAsyncAsTask cancellationToken
......@@ -10,7 +10,6 @@ open Microsoft.CodeAnalysis.Classification
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.FSharp.Editor
open RoslynHelpers
[<TestFixture>][<Category "Roslyn Services">]
type ColorizationServiceTests() =
......@@ -36,7 +35,6 @@ type ColorizationServiceTests() =
| None -> Assert.Fail("Cannot find colorization data for end of marker")
| Some(classifiedSpan) -> Assert.AreEqual(classificationType, classifiedSpan.ClassificationType, "Classification data doesn't match for end of marker")
[<Test>]
member this.Comment_SingleLine() =
this.VerifyColorizerAtEndOfMarker(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册