Skip to content

Commit fd874f2

Browse files
authored
Merge pull request #1534 from OmarTawfik/roslyn-completion
Added Roslyn Completion Service
2 parents 82a3d0f + d831740 commit fd874f2

39 files changed

+458
-52
lines changed

packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<package id="Microsoft.VisualStudio.Shell.Design" version="14.3.25407" targetFramework="net46" />
3636
<package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net46" />
3737
<package id="Microsoft.VisualStudio.Language.StandardClassification" version="14.3.25407" targetFramework="net46" />
38+
<package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net46" />
3839
<package id="Microsoft.VisualStudio.Designer.Interfaces" version="1.1.4322" />
3940
<package id="Roslyn.Microsoft.VisualStudio.ComponentModelHost" version="0.0.2" targetFramework="net46" />
4041
<package id="Microsoft.VisualFSharp.Microsoft.VisualStudio.Shell.UI.Internal" version="14.0.25420" targetFramework="net46" />

vsintegration/src/FSharp.Editor/ColorizationService.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ type internal FSharpColorizationService() =
140140
| Some(options) ->
141141
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
142142
if sourceTextTask.Status = TaskStatus.RanToCompletion then
143-
result.AddRange(FSharpColorizationService.GetColorizationData(sourceTextTask.Result, textSpan, None, defines, cancellationToken))
143+
result.AddRange(FSharpColorizationService.GetColorizationData(sourceTextTask.Result, textSpan, Some(document.FilePath), defines, cancellationToken))
144144
| None -> ()
145145
, cancellationToken)
146146

@@ -153,7 +153,8 @@ type internal FSharpColorizationService() =
153153
let options = CommonRoslynHelpers.GetFSharpProjectOptionsForRoslynProject(document.Project)
154154
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
155155
let! parseResults = FSharpChecker.Instance.ParseFileInProject(document.Name, sourceText.ToString(), options)
156-
let! checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, document.Name, 0, textSpan.ToString(), options)
156+
let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask
157+
let! checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, document.Name, textVersion.GetHashCode(), textSpan.ToString(), options)
157158
158159
let extraColorizationData = match checkResultsAnswer with
159160
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"

vsintegration/src/FSharp.Editor/CommonRoslynHelpers.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ module internal CommonRoslynHelpers =
1818
let endPosition = sourceText.Lines.[range.EndLine - 1].Start + range.EndColumn
1919
TextSpan(startPosition, endPosition - startPosition)
2020

21+
let GetTaskAction(computation: Async<unit>) =
22+
// Shortcut due to nonstandard way of converting Async<unit> to Task
23+
let action() =
24+
try
25+
computation |> Async.RunSynchronously
26+
with ex ->
27+
Assert.Exception(ex.GetBaseException())
28+
raise(ex.GetBaseException())
29+
Action action
30+
2131
let GetCompletedTaskResult(task: Task<'TResult>) =
2232
if task.Status = TaskStatus.RanToCompletion then
2333
task.Result
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System
6+
open System.Composition
7+
open System.Collections.Concurrent
8+
open System.Collections.Generic
9+
open System.Collections.Immutable
10+
open System.Threading
11+
open System.Threading.Tasks
12+
open System.Linq
13+
open System.Runtime.CompilerServices
14+
15+
open Microsoft.CodeAnalysis
16+
open Microsoft.CodeAnalysis.Completion
17+
open Microsoft.CodeAnalysis.Classification
18+
open Microsoft.CodeAnalysis.Editor
19+
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
20+
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
21+
open Microsoft.CodeAnalysis.Formatting
22+
open Microsoft.CodeAnalysis.Host.Mef
23+
open Microsoft.CodeAnalysis.Options
24+
open Microsoft.CodeAnalysis.Text
25+
26+
open Microsoft.VisualStudio.FSharp.LanguageService
27+
open Microsoft.VisualStudio.Text
28+
open Microsoft.VisualStudio.Text.Tagging
29+
open Microsoft.VisualStudio.Shell
30+
open Microsoft.VisualStudio.Shell.Interop
31+
32+
open Microsoft.FSharp.Compiler.Parser
33+
open Microsoft.FSharp.Compiler.Range
34+
open Microsoft.FSharp.Compiler.SourceCodeServices
35+
36+
type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SVsServiceProvider) =
37+
inherit CompletionProvider()
38+
39+
static let completionTriggers = [ '.' ]
40+
static let declarationItemsCache = ConditionalWeakTable<string, FSharpDeclarationListItem>()
41+
42+
let xmlMemberIndexService = serviceProvider.GetService(typeof<IVsXMLMemberIndexService>) :?> IVsXMLMemberIndexService
43+
let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE)
44+
45+
static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, filePath: string, defines: string list) =
46+
// Skip if we are at the start of a document
47+
if caretPosition = 0 then
48+
false
49+
50+
// Skip if it was triggered by an operation other than insertion
51+
else if not (trigger = CompletionTriggerKind.Insertion) then
52+
false
53+
54+
// Skip if we are not on a completion trigger
55+
else if not (completionTriggers |> Seq.contains(sourceText.[caretPosition - 1])) then
56+
false
57+
58+
// Trigger completion if we are on a valid classification type
59+
else
60+
let triggerPosition = caretPosition - 1
61+
let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition)
62+
let classifiedSpanOption =
63+
FSharpColorizationService.GetColorizationData(sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None)
64+
|> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition))
65+
66+
match classifiedSpanOption with
67+
| None -> false
68+
| Some(classifiedSpan) ->
69+
match classifiedSpan.ClassificationType with
70+
| ClassificationTypeNames.Comment -> false
71+
| ClassificationTypeNames.StringLiteral -> false
72+
| ClassificationTypeNames.ExcludedCode -> false
73+
| _ -> true // anything else is a valid classification type
74+
75+
static member ProvideCompletionsAsyncAux(sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async {
76+
let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options)
77+
let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options)
78+
let checkFileResults = match checkFileAnswer with
79+
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"
80+
| FSharpCheckFileAnswer.Succeeded(results) -> results
81+
82+
let textLine = sourceText.Lines.GetLineFromPosition(caretPosition)
83+
let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based
84+
let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), caretPosition - textLine.Start - 1)
85+
let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), textLineNumber, caretPosition, textLine.ToString(), qualifyingNames, partialName)
86+
87+
let results = List<CompletionItem>()
88+
89+
for declarationItem in declarations.Items do
90+
let completionItem = CompletionItem.Create(declarationItem.Name)
91+
declarationItemsCache.Add(completionItem.DisplayText, declarationItem)
92+
results.Add(completionItem)
93+
94+
return results
95+
}
96+
97+
98+
override this.ShouldTriggerCompletion(sourceText: SourceText, caretPosition: int, trigger: CompletionTrigger, _: OptionSet) =
99+
let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container)
100+
let document = workspace.CurrentSolution.GetDocument(documentId)
101+
102+
match FSharpLanguageService.GetOptions(document.Project.Id) with
103+
| None -> false
104+
| Some(options) ->
105+
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
106+
FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, document.FilePath, defines)
107+
108+
override this.ProvideCompletionsAsync(context: Microsoft.CodeAnalysis.Completion.CompletionContext) =
109+
let computation = async {
110+
match FSharpLanguageService.GetOptions(context.Document.Project.Id) with
111+
| Some(options) ->
112+
let! sourceText = context.Document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask
113+
let! textVersion = context.Document.GetTextVersionAsync(context.CancellationToken) |> Async.AwaitTask
114+
let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(sourceText, context.Position, options, context.Document.FilePath, textVersion.GetHashCode())
115+
context.AddItems(results)
116+
| None -> ()
117+
}
118+
119+
Task.Run(CommonRoslynHelpers.GetTaskAction(computation), context.CancellationToken)
120+
121+
override this.GetDescriptionAsync(_: Document, completionItem: CompletionItem, cancellationToken: CancellationToken): Task<CompletionDescription> =
122+
let computation = async {
123+
let exists, declarationItem = declarationItemsCache.TryGetValue(completionItem.DisplayText)
124+
if exists then
125+
let! description = declarationItem.DescriptionTextAsync
126+
let datatipText = XmlDocumentation.BuildDataTipText(documentationBuilder, description)
127+
return CompletionDescription.FromText(datatipText)
128+
else
129+
return CompletionDescription.Empty
130+
}
131+
132+
Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken)
133+
.ContinueWith(CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System
6+
open System.Composition
7+
open System.Collections.Concurrent
8+
open System.Collections.Generic
9+
open System.Collections.Immutable
10+
open System.Threading
11+
open System.Threading.Tasks
12+
open System.Linq
13+
14+
open Microsoft.CodeAnalysis
15+
open Microsoft.CodeAnalysis.Completion
16+
open Microsoft.CodeAnalysis.Editor
17+
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
18+
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
19+
open Microsoft.CodeAnalysis.Formatting
20+
open Microsoft.CodeAnalysis.Host
21+
open Microsoft.CodeAnalysis.Host.Mef
22+
open Microsoft.CodeAnalysis.Text
23+
24+
open Microsoft.VisualStudio.FSharp.LanguageService
25+
open Microsoft.VisualStudio.Text
26+
open Microsoft.VisualStudio.Text.Tagging
27+
open Microsoft.VisualStudio.Shell
28+
29+
open Microsoft.FSharp.Compiler.Parser
30+
open Microsoft.FSharp.Compiler.SourceCodeServices
31+
open Microsoft.FSharp.Compiler.Range
32+
33+
type internal FSharpCompletionService(workspace: Workspace, serviceProvider: SVsServiceProvider) =
34+
inherit CompletionServiceWithProviders(workspace)
35+
36+
let builtInProviders = ImmutableArray.Create<CompletionProvider>(FSharpCompletionProvider(workspace, serviceProvider))
37+
let completionRules = CompletionRules.Default.WithDismissIfEmpty(true).WithDismissIfLastCharacterDeleted(true).WithDefaultEnterKeyRule(EnterKeyRule.Never)
38+
39+
override this.Language = FSharpCommonConstants.FSharpLanguageName
40+
override this.GetBuiltInProviders() = builtInProviders
41+
override this.GetRules() = completionRules
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System
6+
open System.Composition
7+
open System.Collections.Concurrent
8+
open System.Collections.Generic
9+
open System.Collections.Immutable
10+
open System.Threading
11+
open System.Threading.Tasks
12+
open System.Linq
13+
14+
open Microsoft.CodeAnalysis
15+
open Microsoft.CodeAnalysis.Completion
16+
open Microsoft.CodeAnalysis.Editor
17+
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
18+
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
19+
open Microsoft.CodeAnalysis.Formatting
20+
open Microsoft.CodeAnalysis.Host
21+
open Microsoft.CodeAnalysis.Host.Mef
22+
open Microsoft.CodeAnalysis.Text
23+
24+
open Microsoft.VisualStudio.FSharp.LanguageService
25+
open Microsoft.VisualStudio.Text
26+
open Microsoft.VisualStudio.Text.Tagging
27+
open Microsoft.VisualStudio.Shell
28+
29+
open Microsoft.FSharp.Compiler.Parser
30+
open Microsoft.FSharp.Compiler.SourceCodeServices
31+
open Microsoft.FSharp.Compiler.Range
32+
33+
[<Shared>]
34+
[<ExportLanguageServiceFactory(typeof<CompletionService>, FSharpCommonConstants.FSharpLanguageName)>]
35+
type internal FSharpCompletionServiceFactory [<ImportingConstructor>] (serviceProvider: SVsServiceProvider) =
36+
interface ILanguageServiceFactory with
37+
member this.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService =
38+
upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider)

vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ type internal FSharpDocumentDiagnosticAnalyzer() =
3939

4040
Diagnostic.Create(descriptor, Location.Create(filePath, correctedTextSpan , linePositionSpan))
4141

42-
static member GetDiagnostics(filePath: string, sourceText: SourceText, options: FSharpProjectOptions, addSemanticErrors: bool) =
42+
static member GetDiagnostics(filePath: string, sourceText: SourceText, textVersionHash: int, options: FSharpProjectOptions, addSemanticErrors: bool) =
4343
let parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options) |> Async.RunSynchronously
4444
let errors =
4545
if addSemanticErrors then
46-
let checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, 0, sourceText.ToString(), options) |> Async.RunSynchronously
46+
let checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options) |> Async.RunSynchronously
4747
match checkResultsAnswer with
4848
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"
4949
| FSharpCheckFileAnswer.Succeeded(results) -> results.Errors
@@ -65,7 +65,8 @@ type internal FSharpDocumentDiagnosticAnalyzer() =
6565
match FSharpLanguageService.GetOptions(document.Project.Id) with
6666
| Some(options) ->
6767
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
68-
return FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document.FilePath, sourceText, options, false)
68+
let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask
69+
return FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document.FilePath, sourceText, textVersion.GetHashCode(), options, false)
6970
| None -> return ImmutableArray<Diagnostic>.Empty
7071
}
7172

@@ -78,7 +79,8 @@ type internal FSharpDocumentDiagnosticAnalyzer() =
7879
match FSharpLanguageService.GetOptions(document.Project.Id) with
7980
| Some(options) ->
8081
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
81-
return FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document.FilePath, sourceText, options, true)
82+
let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask
83+
return FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document.FilePath, sourceText, textVersion.GetHashCode(), options, true)
8284
| None -> return ImmutableArray<Diagnostic>.Empty
8385
}
8486

vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
<Compile Include="DocumentDiagnosticAnalyzer.fs">
5656
<Link>Diagnostics\DocumentDiagnosticAnalyzer.fs</Link>
5757
</Compile>
58+
<Compile Include="CompletionProvider.fs">
59+
<Link>Completion\CompletionProvider.fs</Link>
60+
</Compile>
61+
<Compile Include="CompletionService.fs">
62+
<Link>Completion\CompletionService.fs</Link>
63+
</Compile>
64+
<Compile Include="CompletionServiceFactory.fs">
65+
<Link>Completion\CompletionServiceFactory.fs</Link>
66+
</Compile>
5867
<Compile Include="ContentType.fs" />
5968
</ItemGroup>
6069
<ItemGroup>
@@ -83,6 +92,8 @@
8392
<Reference Include="System" />
8493
<Reference Include="PresentationCore" />
8594
<Reference Include="System.ComponentModel.Composition" />
95+
<Reference Include="EnvDTE.dll" />
96+
<Reference Include="EnvDTE80.dll" />
8697
<Reference Include="Microsoft.VisualStudio.Threading, Version=$(RoslynVSBinariesVersion).0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
8798
<HintPath>$(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
8899
</Reference>

0 commit comments

Comments
 (0)