Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 59 additions & 41 deletions src/CSharpLanguageServer/Conversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ module Uri =
/// "#/ProjDir" is Fragment instead of part of LocalPath.
let unescape (uri: string) = uri.Replace("%3a", ":", true, null)

let toPath (uri: string) = Uri.UnescapeDataString(Uri(unescape(uri)).LocalPath)
let toPath (uri: string) =
Uri.UnescapeDataString(Uri(unescape uri).LocalPath)

let fromPath (path: string) =
let metadataPrefix = "$metadata$/"
if path.StartsWith(metadataPrefix) then
"csharp:/metadata/" + path.Substring(metadataPrefix.Length)

if path.StartsWith metadataPrefix then
"csharp:/metadata/" + path.Substring metadataPrefix.Length
else
Uri(path).ToString()

let toWorkspaceFolder(uri: string): WorkspaceFolder =
let toWorkspaceFolder (uri: string) : WorkspaceFolder =
{ Uri = uri
Name = Uri.UnescapeDataString(Uri(unescape(uri)).Segments |> Array.last) }
Name = Uri.UnescapeDataString(Uri(unescape uri).Segments |> Array.last) }


module Path =
Expand All @@ -40,36 +42,38 @@ module Path =


module Position =
let fromLinePosition (pos: LinePosition): Position =
{ Line = uint32 pos.Line ; Character = uint32 pos.Character }
let fromLinePosition (pos: LinePosition) : Position =
{ Line = uint32 pos.Line
Character = uint32 pos.Character }

let toLinePosition (lines: TextLineCollection) (pos: Position): LinePosition =
if (int pos.Line) >= lines.Count then
let toLinePosition (lines: TextLineCollection) (pos: Position) : LinePosition =
if int pos.Line >= lines.Count then
LinePosition(lines.Count - 1, lines[lines.Count - 1].EndIncludingLineBreak - lines[lines.Count - 1].Start)
else
LinePosition(int pos.Line, int pos.Character)

let toRoslynPosition (lines: TextLineCollection) = toLinePosition lines >> lines.GetPosition
let toRoslynPosition (lines: TextLineCollection) =
toLinePosition lines >> lines.GetPosition


module Range =
let toLinePositionSpan (lines: TextLineCollection) (range: Range): LinePositionSpan =
LinePositionSpan(
Position.toLinePosition lines range.Start,
Position.toLinePosition lines range.End)
let toLinePositionSpan (lines: TextLineCollection) (range: Range) : LinePositionSpan =
LinePositionSpan(Position.toLinePosition lines range.Start, Position.toLinePosition lines range.End)

let fromLinePositionSpan (pos: LinePositionSpan): Range =
let fromLinePositionSpan (pos: LinePositionSpan) : Range =
{ Start = Position.fromLinePosition pos.Start
End = Position.fromLinePosition pos.End }

let toTextSpan (lines: TextLineCollection) = toLinePositionSpan lines >> lines.GetTextSpan
let toTextSpan (lines: TextLineCollection) =
toLinePositionSpan lines >> lines.GetTextSpan

let fromTextSpan (lines: TextLineCollection) = lines.GetLinePositionSpan >> fromLinePositionSpan
let fromTextSpan (lines: TextLineCollection) =
lines.GetLinePositionSpan >> fromLinePositionSpan


module Location =
let fromRoslynLocation (loc: Microsoft.CodeAnalysis.Location): option<Location> =
let toLspLocation (path: string) span: Location =
let fromRoslynLocation (loc: Microsoft.CodeAnalysis.Location) : option<Location> =
let toLspLocation (path: string) span : Location =
{ Uri = path |> Path.toUri
Range = span |> Range.fromLinePositionSpan }

Expand All @@ -78,14 +82,18 @@ module Location =
let mappedSourceLocation =
loc.GetMappedLineSpan()
|> Some
|> Option.bind (fun mappedLoc -> if mappedLoc.IsValid && File.Exists(mappedLoc.Path) then Some mappedLoc else None)
|> Option.bind (fun mappedLoc ->
if mappedLoc.IsValid && File.Exists mappedLoc.Path then
Some mappedLoc
else
None)
|> Option.map (fun mappedLoc -> toLspLocation mappedLoc.Path mappedLoc.Span)

let sourceLocation =
loc.SourceTree
|> Option.ofObj
|> Option.map _.FilePath
|> Option.bind (fun filePath -> if File.Exists(filePath) then Some filePath else None)
|> Option.bind (fun filePath -> if File.Exists filePath then Some filePath else None)
|> Option.map (fun filePath -> toLspLocation filePath (loc.GetLineSpan().Span))

mappedSourceLocation |> Option.orElse sourceLocation
Expand All @@ -94,17 +102,21 @@ module Location =


module TextEdit =
let fromTextChange (lines: TextLineCollection) (changes: TextChange): TextEdit =
let fromTextChange (lines: TextLineCollection) (changes: TextChange) : TextEdit =
{ Range = changes.Span |> Range.fromTextSpan lines
NewText = changes.NewText }


module SymbolKind =
let fromSymbol (symbol: ISymbol): SymbolKind =
let fromSymbol (symbol: ISymbol) : SymbolKind =
match symbol with
| :? ILocalSymbol -> SymbolKind.Variable
| :? IFieldSymbol as fs ->
if not(isNull fs.ContainingType) && fs.ContainingType.TypeKind = TypeKind.Enum && fs.HasConstantValue then
if
not (isNull fs.ContainingType)
&& fs.ContainingType.TypeKind = TypeKind.Enum
&& fs.HasConstantValue
then
SymbolKind.EnumMember
else
SymbolKind.Field
Expand Down Expand Up @@ -133,7 +145,7 @@ module SymbolKind =


module SymbolName =
let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol): string = symbol.ToDisplayString(format)
let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol) : string = symbol.ToDisplayString format


module CallHierarchyItem =
Expand All @@ -151,12 +163,12 @@ module CallHierarchyItem =
miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes
)

let fromSymbolAndLocation (symbol: ISymbol) (location: Location): CallHierarchyItem =
let fromSymbolAndLocation (symbol: ISymbol) (location: Location) : CallHierarchyItem =
let kind = SymbolKind.fromSymbol symbol
let containingType = (symbol.ContainingType :> ISymbol) |> Option.ofObj
let containingNamespace = (symbol.ContainingNamespace :> ISymbol) |> Option.ofObj

{ Name = symbol.ToDisplayString(displayStyle)
{ Name = symbol.ToDisplayString displayStyle
Kind = kind
Tags = None
Detail =
Expand All @@ -168,7 +180,10 @@ module CallHierarchyItem =
SelectionRange = location.Range
Data = None }

let fromSymbol (wmResolveSymbolLocations: ISymbol -> Project option -> Async<list<Location>>) (symbol: ISymbol): Async<CallHierarchyItem list> =
let fromSymbol
(wmResolveSymbolLocations: ISymbol -> Project option -> Async<list<Location>>)
(symbol: ISymbol)
: Async<CallHierarchyItem list> =
wmResolveSymbolLocations symbol None
|> Async.map (List.map (fromSymbolAndLocation symbol))

Expand All @@ -187,12 +202,12 @@ module TypeHierarchyItem =
miscellaneousOptions = SymbolDisplayMiscellaneousOptions.UseSpecialTypes
)

let fromSymbolAndLocation (symbol: ISymbol) (location: Location): TypeHierarchyItem =
let fromSymbolAndLocation (symbol: ISymbol) (location: Location) : TypeHierarchyItem =
let kind = SymbolKind.fromSymbol symbol
let containingType = (symbol.ContainingType :> ISymbol) |> Option.ofObj
let containingNamespace = (symbol.ContainingNamespace :> ISymbol) |> Option.ofObj

{ Name = symbol.ToDisplayString(displayStyle)
{ Name = symbol.ToDisplayString displayStyle
Kind = kind
Tags = None
Detail =
Expand All @@ -204,12 +219,15 @@ module TypeHierarchyItem =
SelectionRange = location.Range
Data = None }

let fromSymbol (wmResolveSymbolLocations: ISymbol -> Project option -> Async<list<Location>>) (symbol: ISymbol): Async<TypeHierarchyItem list> =
let fromSymbol
(wmResolveSymbolLocations: ISymbol -> Project option -> Async<list<Location>>)
(symbol: ISymbol)
: Async<TypeHierarchyItem list> =
wmResolveSymbolLocations symbol None
|> Async.map (List.map (fromSymbolAndLocation symbol))

module SymbolInformation =
let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol): SymbolInformation list =
let fromSymbol (format: SymbolDisplayFormat) (symbol: ISymbol) : SymbolInformation list =
let toSymbolInformation loc =
{ Name = SymbolName.fromSymbol format symbol
Kind = SymbolKind.fromSymbol symbol
Expand All @@ -227,7 +245,7 @@ module SymbolInformation =


module DiagnosticSeverity =
let fromRoslynDiagnosticSeverity (sev: Microsoft.CodeAnalysis.DiagnosticSeverity): DiagnosticSeverity =
let fromRoslynDiagnosticSeverity (sev: Microsoft.CodeAnalysis.DiagnosticSeverity) : DiagnosticSeverity =
match sev with
| Microsoft.CodeAnalysis.DiagnosticSeverity.Info -> DiagnosticSeverity.Information
| Microsoft.CodeAnalysis.DiagnosticSeverity.Warning -> DiagnosticSeverity.Warning
Expand All @@ -236,11 +254,12 @@ module DiagnosticSeverity =


module Diagnostic =
let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic): Diagnostic =
let fromRoslynDiagnostic (diagnostic: Microsoft.CodeAnalysis.Diagnostic) : Diagnostic =
let diagnosticCodeUrl = diagnostic.Descriptor.HelpLinkUri |> Option.ofObj

{ Range = diagnostic.Location.GetLineSpan().Span |> Range.fromLinePositionSpan
Severity = Some (diagnostic.Severity |> DiagnosticSeverity.fromRoslynDiagnosticSeverity)
Code = Some (U2.C2 diagnostic.Id)
Severity = Some(diagnostic.Severity |> DiagnosticSeverity.fromRoslynDiagnosticSeverity)
Code = Some(U2.C2 diagnostic.Id)
CodeDescription = diagnosticCodeUrl |> Option.map (fun x -> { Href = x |> URI })
Source = Some "lsp"
Message = diagnostic.GetMessage()
Expand All @@ -251,16 +270,15 @@ module Diagnostic =


module CompletionContext =
let toCompletionTrigger (context: CompletionContext option): Completion.CompletionTrigger =
let toCompletionTrigger (context: CompletionContext option) : CompletionTrigger =
context
|> Option.bind (fun ctx ->
match ctx.TriggerKind with
| CompletionTriggerKind.Invoked
| CompletionTriggerKind.TriggerForIncompleteCompletions ->
Some Completion.CompletionTrigger.Invoke
| CompletionTriggerKind.TriggerForIncompleteCompletions -> Some CompletionTrigger.Invoke
| CompletionTriggerKind.TriggerCharacter ->
ctx.TriggerCharacter
|> Option.map Seq.head
|> Option.map Completion.CompletionTrigger.CreateInsertionTrigger
|> Option.map CompletionTrigger.CreateInsertionTrigger
| _ -> None)
|> Option.defaultValue (Completion.CompletionTrigger.Invoke)
|> Option.defaultValue CompletionTrigger.Invoke
67 changes: 37 additions & 30 deletions src/CSharpLanguageServer/DocumentationUtil.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module DocumentationUtil =
Types: (string * XElement) list
Remarks: XElement list
OtherLines: XElement list }

static member Default =
{ Summary = []
Params = []
Expand All @@ -26,14 +27,16 @@ module DocumentationUtil =
OtherLines = [] }

let parseCref (cref: string) =
let parts = cref.Split(':')
let parts = cref.Split ':'

match parts.Length with
| 1 -> cref
| _ -> String.Join(":", parts |> Seq.skip 1)

let normalizeWhitespace (s: string) =
let mutable modified = s
let mutable prevModified = ""

while modified <> prevModified do
prevModified <- modified
modified <- modified.Replace(" ", " ").Replace("\r\n", " ").Replace("\n", " ")
Expand All @@ -44,15 +47,15 @@ module DocumentationUtil =

let formatSeeElement (e: XElement) =
let crefMaybe =
e.Attribute(XName.Get("cref"))
e.Attribute(XName.Get "cref")
|> Option.ofObj
|> Option.map (fun x -> x.Value)
|> Option.map parseCref
|> Option.map (fun s -> sprintf "``%s``" s)
|> Option.toList

let langWordMaybe =
e.Attribute(XName.Get("langword"))
e.Attribute(XName.Get "langword")
|> Option.ofObj
|> Option.map (fun x -> sprintf "``%s``" x.Value)
|> Option.toList
Expand All @@ -66,15 +69,11 @@ module DocumentationUtil =
| "c" -> [ sprintf "``%s``" e.Value ]
| "see" -> formatSeeElement e
| "paramref" ->
e.Attribute(XName.Get("name"))
e.Attribute(XName.Get "name")
|> Option.ofObj
|> Option.map (fun x -> sprintf "``%s``" x.Value)
|> Option.toList
| "para" ->
e.Nodes()
|> Seq.collect formatTextNode
|> Seq.append [ "\n\n" ]
|> List.ofSeq
| "para" -> e.Nodes() |> Seq.collect formatTextNode |> Seq.append [ "\n\n" ] |> List.ofSeq
| _ -> [ e.Value ]
| :? XText as t -> [ t.Value |> normalizeWhitespace ]
| _ -> []
Expand All @@ -92,46 +91,53 @@ module DocumentationUtil =
{ comment with Remarks = newRemarks }

| "param" ->
let name = n.Attribute(XName.Get("name"))
|> Option.ofObj
|> Option.map (fun a -> a.Value)
|> Option.defaultValue "(unspecified)"
let name =
n.Attribute(XName.Get "name")
|> Option.ofObj
|> Option.map (fun a -> a.Value)
|> Option.defaultValue "(unspecified)"

{ comment with Params = comment.Params |> List.append [ (name, n) ] }
{ comment with
Params = comment.Params |> List.append [ (name, n) ] }

| "returns" ->
{ comment with Returns = comment.Returns |> List.append [ n ] }
{ comment with
Returns = comment.Returns |> List.append [ n ] }

| "exception" ->
let name = n.Attribute(XName.Get("cref"))
|> Option.ofObj
|> Option.map (fun a -> parseCref a.Value)
|> Option.defaultValue "(unspecified)"
let name =
n.Attribute(XName.Get "cref")
|> Option.ofObj
|> Option.map (fun a -> parseCref a.Value)
|> Option.defaultValue "(unspecified)"

{ comment with
Exceptions = comment.Exceptions |> List.append [ (name, n) ] }

| "typeparam" ->
let name = n.Attribute(XName.Get("name"))
|> Option.ofObj
|> Option.map (fun a -> a.Value)
|> Option.defaultValue "(unspecified)"
let name =
n.Attribute(XName.Get "name")
|> Option.ofObj
|> Option.map (fun a -> a.Value)
|> Option.defaultValue "(unspecified)"

{ comment with Types = comment.Types |> List.append [ (name, n) ] }
{ comment with
Types = comment.Types |> List.append [ (name, n) ] }

| _ ->
{ comment with OtherLines = comment.OtherLines |> List.append [ n ] }
{ comment with
OtherLines = comment.OtherLines |> List.append [ n ] }


let parseComment xmlDocumentation: TripleSlashComment =
let parseComment xmlDocumentation : TripleSlashComment =
let doc = XDocument.Parse("<docroot>" + xmlDocumentation + "</docroot>")

let unwrapDocRoot (root: XElement) =
let elementNames (el: XElement) =
el.Elements() |> Seq.map (fun e -> e.Name.LocalName) |> List.ofSeq

match elementNames root with
| [ "member" ] -> root.Element(XName.Get("member"))
| [ "member" ] -> root.Element(XName.Get "member")
| _ -> root

doc.Root
Expand Down Expand Up @@ -185,10 +191,11 @@ module DocumentationUtil =
let comment = parseComment (sym.GetDocumentationCommentXml())
let formattedDocLines = formatComment comment

formattedDocLines |> (fun ss -> String.Join("\n", ss))
formattedDocLines |> fun ss -> String.Join("\n", ss)

let markdownDocForSymbolWithSignature (sym: ISymbol) =
let symbolName = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat sym
let symbolName =
SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat sym

let symbolInfoLines =
match symbolName with
Expand All @@ -206,4 +213,4 @@ module DocumentationUtil =
[]
)
|> Seq.append symbolInfoLines
|> (fun ss -> String.Join("\n", ss))
|> fun ss -> String.Join("\n", ss)
Loading
Loading