Skip to content

Commit ff3521d

Browse files
committed
squash
1 parent f51cd9d commit ff3521d

File tree

26 files changed

+518
-151
lines changed

26 files changed

+518
-151
lines changed

src/CSharpLanguageServer/CSharpLanguageServer.fsproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
<PackageReadmeFile>README.md</PackageReadmeFile>
1818
<ChangelogFile>CHANGELOG.md</ChangelogFile>
1919
<Nullable>enable</Nullable>
20-
<MSBuildTreatWarningsAsErrors>true</MSBuildTreatWarningsAsErrors>
2120
</PropertyGroup>
2221

2322
<ItemGroup>

src/CSharpLanguageServer/Conversions.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ module Location =
8888
|> Option.bind (fun filePath -> if File.Exists filePath then Some filePath else None)
8989
|> Option.map (fun filePath -> toLspLocation filePath (loc.GetLineSpan().Span))
9090

91+
//Console.Error.WriteLine("loc={0}; mapped={1}; source={2}", loc, mappedSourceLocation, sourceLocation)
92+
9193
mappedSourceLocation |> Option.orElse sourceLocation
9294

9395
| _ -> None

src/CSharpLanguageServer/Handlers/References.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ open Ionide.LanguageServerProtocol.JsonRpc
55

66
open CSharpLanguageServer.State
77
open CSharpLanguageServer.Conversions
8+
open CSharpLanguageServer.Logging
89

910
[<RequireQualifiedAccess>]
1011
module References =
12+
let private logger = Logging.getLoggerByName "References"
13+
1114
let provider (_: ClientCapabilities) : U2<bool, ReferenceOptions> option = Some(U2.C1 true)
1215

1316
let handle (context: ServerRequestContext) (p: ReferenceParams) : AsyncLspResult<Location[] option> = async {
Lines changed: 147 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
namespace CSharpLanguageServer.Handlers
22

33
open System
4+
open System.Text
5+
open System.IO
46

7+
open Microsoft.Extensions.Logging
58
open Microsoft.CodeAnalysis.Text
69
open Ionide.LanguageServerProtocol.Types
710
open Ionide.LanguageServerProtocol.JsonRpc
@@ -21,7 +24,6 @@ module TextDocumentSync =
2124
(changes: TextDocumentContentChangeEvent[])
2225
(initialSourceText: SourceText)
2326
=
24-
2527
let applyLspContentChangeOnRoslynSourceText (sourceText: SourceText) (change: TextDocumentContentChangeEvent) =
2628
match change with
2729
| U2.C1 change ->
@@ -42,74 +44,160 @@ module TextDocumentSync =
4244
Change = Some TextDocumentSyncKind.Incremental }
4345
|> Some
4446

45-
4647
let didOpen (context: ServerRequestContext) (openParams: DidOpenTextDocumentParams) : Async<LspResult<unit>> =
47-
match context.GetDocumentForUriOfType AnyDocument openParams.TextDocument.Uri with
48-
| Some(doc, docType) ->
49-
match docType with
50-
| UserDocument ->
51-
// we want to load the document in case it has been changed since we have the solution loaded
52-
// also, as a bonus we can recover from corrupted document view in case document in roslyn solution
53-
// went out of sync with editor
54-
let updatedDoc = SourceText.From(openParams.TextDocument.Text) |> doc.WithText
48+
if openParams.TextDocument.Uri.EndsWith(".cshtml") then
49+
let u = openParams.TextDocument.Uri |> string
50+
let uri = Uri(u.Replace("%3A", ":", true, null))
51+
52+
let matchingAdditionalDoc =
53+
context.Solution.Projects
54+
|> Seq.collect (fun p -> p.AdditionalDocuments)
55+
|> Seq.filter (fun d -> Uri(d.FilePath, UriKind.Absolute) = uri)
56+
|> List.ofSeq
57+
58+
let doc =
59+
if matchingAdditionalDoc.Length = 1 then
60+
matchingAdditionalDoc |> Seq.head |> Some
61+
else
62+
None
63+
64+
let newSourceText = SourceText.From(openParams.TextDocument.Text, Encoding.UTF8)
65+
66+
match doc with
67+
| Some doc ->
68+
let updatedDoc =
69+
doc.Project
70+
|> _.RemoveAdditionalDocument(doc.Id)
71+
|> _.AddAdditionalDocument(doc.Name, newSourceText, doc.Folders, doc.FilePath)
5572

5673
context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
5774
context.Emit(SolutionChange updatedDoc.Project.Solution)
5875

59-
Ok() |> async.Return
76+
| None ->
77+
let cshtmlPath = Uri.toPath openParams.TextDocument.Uri
78+
let project = getProjectForPathOnSolution context.Solution cshtmlPath
6079

61-
| _ -> Ok() |> async.Return
80+
match project with
81+
| Some project ->
82+
let projectBaseDir = Path.GetDirectoryName(project.FilePath)
83+
let relativePath = Path.GetRelativePath(projectBaseDir, cshtmlPath)
6284

63-
| None ->
64-
let docFilePathMaybe = Util.tryParseFileUri openParams.TextDocument.Uri
85+
let folders = relativePath.Split(Path.DirectorySeparatorChar)
6586

66-
match docFilePathMaybe with
67-
| Some docFilePath -> async {
68-
// ok, this document is not in solution, register a new document
69-
let! newDocMaybe = tryAddDocument logger docFilePath openParams.TextDocument.Text context.Solution
87+
let folders = folders |> Seq.take (folders.Length - 1)
88+
89+
let newDoc =
90+
project.AddAdditionalDocument(Path.GetFileName(cshtmlPath), newSourceText, folders, cshtmlPath)
7091

71-
match newDocMaybe with
72-
| Some newDoc ->
7392
context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
7493
context.Emit(SolutionChange newDoc.Project.Solution)
94+
()
7595

7696
| None -> ()
7797

78-
return Ok()
79-
}
98+
Ok() |> async.Return
99+
else
100+
match context.GetDocumentForUriOfType AnyDocument openParams.TextDocument.Uri with
101+
| Some(doc, docType) ->
102+
match docType with
103+
| UserDocument ->
104+
// we want to load the document in case it has been changed since we have the solution loaded
105+
// also, as a bonus we can recover from corrupted document view in case document in roslyn solution
106+
// went out of sync with editor
107+
let updatedDoc = SourceText.From(openParams.TextDocument.Text) |> doc.WithText
108+
109+
context.Emit(OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
110+
context.Emit(SolutionChange updatedDoc.Project.Solution)
111+
112+
Ok() |> async.Return
113+
114+
| _ -> Ok() |> async.Return
115+
116+
| None ->
117+
let docFilePathMaybe = Util.tryParseFileUri openParams.TextDocument.Uri
118+
119+
match docFilePathMaybe with
120+
| Some docFilePath -> async {
121+
// ok, this document is not in solution, register a new document
122+
let! newDocMaybe = tryAddDocument logger docFilePath openParams.TextDocument.Text context.Solution
123+
124+
match newDocMaybe with
125+
| Some newDoc ->
126+
context.Emit(
127+
OpenDocAdd(openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now)
128+
)
129+
130+
context.Emit(SolutionChange newDoc.Project.Solution)
131+
132+
| None -> ()
80133

81-
| None -> Ok() |> async.Return
134+
return Ok()
135+
}
136+
| None -> Ok() |> async.Return
82137

83138
let didChange (context: ServerRequestContext) (changeParams: DidChangeTextDocumentParams) : Async<LspResult<unit>> = async {
84-
let docMaybe = context.GetUserDocument changeParams.TextDocument.Uri
139+
if changeParams.TextDocument.Uri.EndsWith(".cshtml") then
140+
let u = changeParams.TextDocument.Uri |> string
141+
let uri = Uri(u.Replace("%3A", ":", true, null))
142+
143+
let matchingAdditionalDoc =
144+
context.Solution.Projects
145+
|> Seq.collect (fun p -> p.AdditionalDocuments)
146+
|> Seq.filter (fun d -> Uri(d.FilePath, UriKind.Absolute) = uri)
147+
|> List.ofSeq
148+
149+
let doc =
150+
if matchingAdditionalDoc.Length = 1 then
151+
matchingAdditionalDoc |> Seq.head |> Some
152+
else
153+
None
154+
155+
match doc with
156+
| None -> ()
157+
| Some doc ->
158+
let! ct = Async.CancellationToken
159+
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
160+
161+
let updatedSourceText =
162+
sourceText
163+
|> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges
85164

86-
match docMaybe with
87-
| None -> ()
88-
| Some doc ->
89-
let! ct = Async.CancellationToken
90-
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
91-
//logMessage (sprintf "TextDocumentDidChange: changeParams: %s" (string changeParams))
92-
//logMessage (sprintf "TextDocumentDidChange: sourceText: %s" (string sourceText))
165+
let updatedDoc =
166+
doc.Project
167+
|> _.RemoveAdditionalDocument(doc.Id)
168+
|> _.AddAdditionalDocument(doc.Name, updatedSourceText, doc.Folders, doc.FilePath)
169+
170+
context.Emit(OpenDocAdd(changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now))
171+
context.Emit(SolutionChange updatedDoc.Project.Solution)
93172

94-
let updatedSourceText =
95-
sourceText
96-
|> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges
173+
return Ok()
174+
else
175+
let docMaybe = context.GetUserDocument changeParams.TextDocument.Uri
97176

98-
let updatedDoc = doc.WithText(updatedSourceText)
177+
match docMaybe with
178+
| None -> ()
179+
| Some doc ->
180+
let! ct = Async.CancellationToken
181+
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
99182

100-
//logMessage (sprintf "TextDocumentDidChange: newSourceText: %s" (string updatedSourceText))
183+
let updatedSourceText =
184+
sourceText
185+
|> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges
101186

102-
let updatedSolution = updatedDoc.Project.Solution
187+
let updatedSolution = doc.WithText(updatedSourceText).Project.Solution
103188

104-
context.Emit(SolutionChange updatedSolution)
105-
context.Emit(OpenDocAdd(changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now))
189+
context.Emit(SolutionChange updatedSolution)
190+
context.Emit(OpenDocAdd(changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now))
106191

107-
return Ok()
192+
return Ok()
108193
}
109194

110195
let didClose (context: ServerRequestContext) (closeParams: DidCloseTextDocumentParams) : Async<LspResult<unit>> =
111-
context.Emit(OpenDocRemove closeParams.TextDocument.Uri)
112-
Ok() |> async.Return
196+
if closeParams.TextDocument.Uri.EndsWith(".cshtml") then
197+
Ok() |> async.Return
198+
else
199+
context.Emit(OpenDocRemove closeParams.TextDocument.Uri)
200+
Ok() |> async.Return
113201

114202
let willSave (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams) : Async<LspResult<unit>> = async {
115203
return Ok()
@@ -122,22 +210,25 @@ module TextDocumentSync =
122210
async { return LspResult.notImplemented<TextEdit[] option> }
123211

124212
let didSave (context: ServerRequestContext) (saveParams: DidSaveTextDocumentParams) : Async<LspResult<unit>> =
125-
// we need to add this file to solution if not already
126-
let doc = context.GetDocument saveParams.TextDocument.Uri
213+
if saveParams.TextDocument.Uri.EndsWith(".cshtml") then
214+
Ok() |> async.Return
215+
else
216+
// we need to add this file to solution if not already
217+
let doc = context.GetDocument saveParams.TextDocument.Uri
127218

128-
match doc with
129-
| Some _ -> Ok() |> async.Return
219+
match doc with
220+
| Some _ -> Ok() |> async.Return
130221

131-
| None -> async {
132-
let docFilePath = Util.parseFileUri saveParams.TextDocument.Uri
133-
let! newDocMaybe = tryAddDocument logger docFilePath saveParams.Text.Value context.Solution
222+
| None -> async {
223+
let docFilePath = Util.parseFileUri saveParams.TextDocument.Uri
224+
let! newDocMaybe = tryAddDocument logger docFilePath saveParams.Text.Value context.Solution
134225

135-
match newDocMaybe with
136-
| Some newDoc ->
137-
context.Emit(OpenDocTouch(saveParams.TextDocument.Uri, DateTime.Now))
138-
context.Emit(SolutionChange newDoc.Project.Solution)
226+
match newDocMaybe with
227+
| Some newDoc ->
228+
context.Emit(OpenDocTouch(saveParams.TextDocument.Uri, DateTime.Now))
229+
context.Emit(SolutionChange newDoc.Project.Solution)
139230

140-
| None -> ()
231+
| None -> ()
141232

142-
return Ok()
143-
}
233+
return Ok()
234+
}

src/CSharpLanguageServer/State/ServerRequestContext.fs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
namespace CSharpLanguageServer.State
22

3+
open System
4+
open System.IO
5+
36
open Microsoft.CodeAnalysis
47
open Microsoft.CodeAnalysis.FindSymbols
58
open Ionide.LanguageServerProtocol.Types
@@ -33,7 +36,8 @@ type ServerRequestContext(requestId: int, state: ServerState, emitServerEvent) =
3336
)
3437
| None -> async.Return()
3538

36-
member this.GetDocumentForUriOfType = getDocumentForUriOfType this.State
39+
member this.GetDocumentForUriOfType docType uri =
40+
getDocumentForUriOfType this.State docType uri
3741

3842
member this.GetUserDocument(u: string) =
3943
this.GetDocumentForUriOfType UserDocument u |> Option.map fst
@@ -135,14 +139,45 @@ type ServerRequestContext(requestId: int, state: ServerState, emitServerEvent) =
135139
}
136140

137141
member this.FindSymbol' (uri: DocumentUri) (pos: Position) : Async<(ISymbol * Project * Document option) option> = async {
138-
match this.GetDocument uri with
139-
| None -> return None
140-
| Some doc ->
141-
let! ct = Async.CancellationToken
142-
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
143-
let position = Position.toRoslynPosition sourceText.Lines pos
144-
let! symbol = SymbolFinder.FindSymbolAtPositionAsync(doc, position, ct) |> Async.AwaitTask
145-
return symbol |> Option.ofObj |> Option.map (fun sym -> sym, doc.Project, Some doc)
142+
if uri.EndsWith(".cshtml") then
143+
match! getRazorDocumentForUri state uri with
144+
| Some(project, compilation, cshtmlPath, cshtmlTree) ->
145+
let model = compilation.GetSemanticModel(cshtmlTree)
146+
147+
let! symbol = async {
148+
let root = cshtmlTree.GetRoot()
149+
150+
// 3. Walk tokens and find one whose mapped span covers the Razor position
151+
let token =
152+
root.DescendantTokens()
153+
|> Seq.tryFind (fun t ->
154+
let span = cshtmlTree.GetMappedLineSpan(t.Span)
155+
156+
span.Path = cshtmlPath
157+
&& span.StartLinePosition.Line <= (int pos.Line)
158+
&& span.EndLinePosition.Line >= (int pos.Line)
159+
&& span.StartLinePosition.Character <= (int pos.Character)
160+
&& span.EndLinePosition.Character >= (int pos.Character))
161+
162+
match token with
163+
| Some t -> return model.GetSymbolInfo(t.Parent).Symbol |> Option.ofObj
164+
| None -> return None
165+
}
166+
167+
match symbol with
168+
| Some symbol -> return Some(symbol, project, None)
169+
| None -> return None
170+
171+
| None -> return None
172+
else
173+
match this.GetDocument uri with
174+
| None -> return None
175+
| Some doc ->
176+
let! ct = Async.CancellationToken
177+
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
178+
let position = Position.toRoslynPosition sourceText.Lines pos
179+
let! symbol = SymbolFinder.FindSymbolAtPositionAsync(doc, position, ct) |> Async.AwaitTask
180+
return symbol |> Option.ofObj |> Option.map (fun sym -> sym, doc.Project, Some doc)
146181
}
147182

148183
member this.FindSymbol (uri: DocumentUri) (pos: Position) : Async<ISymbol option> =

0 commit comments

Comments
 (0)