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
1 change: 1 addition & 0 deletions packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<package id="Microsoft.VisualStudio.Shell.Design" version="14.3.25407" targetFramework="net46" />
<package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net46" />
<package id="Microsoft.VisualStudio.Language.StandardClassification" version="14.3.25407" targetFramework="net46" />
<package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net46" />
<package id="Microsoft.VisualStudio.Designer.Interfaces" version="1.1.4322" />
<package id="Roslyn.Microsoft.VisualStudio.ComponentModelHost" version="0.0.2" targetFramework="net46" />
<package id="Microsoft.VisualFSharp.Microsoft.VisualStudio.Shell.UI.Internal" version="14.0.25420" targetFramework="net46" />
Expand Down
5 changes: 3 additions & 2 deletions vsintegration/src/FSharp.Editor/ColorizationService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ type internal FSharpColorizationService() =
| Some(options) ->
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
if sourceTextTask.Status = TaskStatus.RanToCompletion then
result.AddRange(FSharpColorizationService.GetColorizationData(sourceTextTask.Result, textSpan, None, defines, cancellationToken))
result.AddRange(FSharpColorizationService.GetColorizationData(sourceTextTask.Result, textSpan, Some(document.FilePath), defines, cancellationToken))
| None -> ()
, cancellationToken)

Expand All @@ -153,7 +153,8 @@ type internal FSharpColorizationService() =
let options = CommonRoslynHelpers.GetFSharpProjectOptionsForRoslynProject(document.Project)
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
let! parseResults = FSharpChecker.Instance.ParseFileInProject(document.Name, sourceText.ToString(), options)
let! checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, document.Name, 0, textSpan.ToString(), options)
let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask
let! checkResultsAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, document.Name, textVersion.GetHashCode(), textSpan.ToString(), options)

let extraColorizationData = match checkResultsAnswer with
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"
Expand Down
10 changes: 10 additions & 0 deletions vsintegration/src/FSharp.Editor/CommonRoslynHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ module internal CommonRoslynHelpers =
let endPosition = sourceText.Lines.[range.EndLine - 1].Start + range.EndColumn
TextSpan(startPosition, endPosition - startPosition)

let GetTaskAction(computation: Async<unit>) =
// Shortcut due to nonstandard way of converting Async<unit> to Task
let action() =
try
computation |> Async.RunSynchronously
with ex ->
Assert.Exception(ex.GetBaseException())
raise(ex.GetBaseException())
Action action

let GetCompletedTaskResult(task: Task<'TResult>) =
if task.Status = TaskStatus.RanToCompletion then
task.Result
Expand Down
133 changes: 133 additions & 0 deletions vsintegration/src/FSharp.Editor/CompletionProvider.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Collections.Concurrent
open System.Collections.Generic
open System.Collections.Immutable
open System.Threading
open System.Threading.Tasks
open System.Linq
open System.Runtime.CompilerServices

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Completion
open Microsoft.CodeAnalysis.Classification
open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
open Microsoft.CodeAnalysis.Formatting
open Microsoft.CodeAnalysis.Host.Mef
open Microsoft.CodeAnalysis.Options
open Microsoft.CodeAnalysis.Text

open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Tagging
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop

open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices

type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SVsServiceProvider) =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serviceProvider: SVsServiceProvider [](start = 61, length = 35)

This is sort of a layering violation. Is there no other way to implement this without accessing other VS layer services?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, no, in the existing implementation (see XmlDocumentation.fs). Can you please explain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the IVsXMLMemberIndexService the only reason for the layer violation? Seems like an abstraction could be added to solve this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general idea for these providers is that they are not dependent on VS, so they can work outside of VS, either to run out of proc or be used within a different host.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We can log a future work item for redesigning this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. @OmarTawfik and I chatted about that easier. For now, this is VS-only, and we can factor it differently after this goes in.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, that was my only hesitation.

inherit CompletionProvider()

static let completionTriggers = [ '.' ]
static let declarationItemsCache = ConditionalWeakTable<string, FSharpDeclarationListItem>()

let xmlMemberIndexService = serviceProvider.GetService(typeof<IVsXMLMemberIndexService>) :?> IVsXMLMemberIndexService
let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService, serviceProvider.DTE)

static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, filePath: string, defines: string list) =
// Skip if we are at the start of a document
if caretPosition = 0 then
false

// Skip if it was triggered by an operation other than insertion
else if not (trigger = CompletionTriggerKind.Insertion) then
false

// Skip if we are not on a completion trigger
else if not (completionTriggers |> Seq.contains(sourceText.[caretPosition - 1])) then
false

// Trigger completion if we are on a valid classification type
else
let triggerPosition = caretPosition - 1
let textLine = sourceText.Lines.GetLineFromPosition(triggerPosition)
let classifiedSpanOption =
FSharpColorizationService.GetColorizationData(sourceText, textLine.Span, Some(filePath), defines, CancellationToken.None)
|> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(triggerPosition))

match classifiedSpanOption with
| None -> false
| Some(classifiedSpan) ->
match classifiedSpan.ClassificationType with
| ClassificationTypeNames.Comment -> false
| ClassificationTypeNames.StringLiteral -> false
| ClassificationTypeNames.ExcludedCode -> false
| _ -> true // anything else is a valid classification type

static member ProvideCompletionsAsyncAux(sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string, textVersionHash: int) = async {
let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options)
let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options)
let checkFileResults = match checkFileAnswer with
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"
| FSharpCheckFileAnswer.Succeeded(results) -> results

let textLine = sourceText.Lines.GetLineFromPosition(caretPosition)
let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based
let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(textLine.ToString(), caretPosition - textLine.Start - 1)
let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), textLineNumber, caretPosition, textLine.ToString(), qualifyingNames, partialName)

let results = List<CompletionItem>()

for declarationItem in declarations.Items do
let completionItem = CompletionItem.Create(declarationItem.Name)
declarationItemsCache.Add(completionItem.DisplayText, declarationItem)
results.Add(completionItem)

return results
}


override this.ShouldTriggerCompletion(sourceText: SourceText, caretPosition: int, trigger: CompletionTrigger, _: OptionSet) =
let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container)
let document = workspace.CurrentSolution.GetDocument(documentId)

match FSharpLanguageService.GetOptions(document.Project.Id) with
| None -> false
| Some(options) ->
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, document.FilePath, defines)

override this.ProvideCompletionsAsync(context: Microsoft.CodeAnalysis.Completion.CompletionContext) =
let computation = async {
match FSharpLanguageService.GetOptions(context.Document.Project.Id) with
| Some(options) ->
let! sourceText = context.Document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask
let! textVersion = context.Document.GetTextVersionAsync(context.CancellationToken) |> Async.AwaitTask
let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(sourceText, context.Position, options, context.Document.FilePath, textVersion.GetHashCode())
context.AddItems(results)
| None -> ()
}

Task.Run(CommonRoslynHelpers.GetTaskAction(computation), context.CancellationToken)

override this.GetDescriptionAsync(_: Document, completionItem: CompletionItem, cancellationToken: CancellationToken): Task<CompletionDescription> =
let computation = async {
let exists, declarationItem = declarationItemsCache.TryGetValue(completionItem.DisplayText)
if exists then
let! description = declarationItem.DescriptionTextAsync
let datatipText = XmlDocumentation.BuildDataTipText(documentationBuilder, description)
return CompletionDescription.FromText(datatipText)
else
return CompletionDescription.Empty
}

Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken)
.ContinueWith(CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)
41 changes: 41 additions & 0 deletions vsintegration/src/FSharp.Editor/CompletionService.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Collections.Concurrent
open System.Collections.Generic
open System.Collections.Immutable
open System.Threading
open System.Threading.Tasks
open System.Linq

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Completion
open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
open Microsoft.CodeAnalysis.Formatting
open Microsoft.CodeAnalysis.Host
open Microsoft.CodeAnalysis.Host.Mef
open Microsoft.CodeAnalysis.Text

open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Tagging
open Microsoft.VisualStudio.Shell

open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range

type internal FSharpCompletionService(workspace: Workspace, serviceProvider: SVsServiceProvider) =
inherit CompletionServiceWithProviders(workspace)

let builtInProviders = ImmutableArray.Create<CompletionProvider>(FSharpCompletionProvider(workspace, serviceProvider))
let completionRules = CompletionRules.Default.WithDismissIfEmpty(true).WithDismissIfLastCharacterDeleted(true).WithDefaultEnterKeyRule(EnterKeyRule.Never)

override this.Language = FSharpCommonConstants.FSharpLanguageName
override this.GetBuiltInProviders() = builtInProviders
override this.GetRules() = completionRules
38 changes: 38 additions & 0 deletions vsintegration/src/FSharp.Editor/CompletionServiceFactory.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Collections.Concurrent
open System.Collections.Generic
open System.Collections.Immutable
open System.Threading
open System.Threading.Tasks
open System.Linq

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Completion
open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
open Microsoft.CodeAnalysis.Formatting
open Microsoft.CodeAnalysis.Host
open Microsoft.CodeAnalysis.Host.Mef
open Microsoft.CodeAnalysis.Text

open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Tagging
open Microsoft.VisualStudio.Shell

open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range

[<Shared>]
[<ExportLanguageServiceFactory(typeof<CompletionService>, FSharpCommonConstants.FSharpLanguageName)>]
type internal FSharpCompletionServiceFactory [<ImportingConstructor>] (serviceProvider: SVsServiceProvider) =
interface ILanguageServiceFactory with
member this.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService =
upcast new FSharpCompletionService(hostLanguageServices.WorkspaceServices.Workspace, serviceProvider)
10 changes: 6 additions & 4 deletions vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ type internal FSharpDocumentDiagnosticAnalyzer() =

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

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

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

Expand Down
11 changes: 11 additions & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
<Compile Include="DocumentDiagnosticAnalyzer.fs">
<Link>Diagnostics\DocumentDiagnosticAnalyzer.fs</Link>
</Compile>
<Compile Include="CompletionProvider.fs">
<Link>Completion\CompletionProvider.fs</Link>
</Compile>
<Compile Include="CompletionService.fs">
<Link>Completion\CompletionService.fs</Link>
</Compile>
<Compile Include="CompletionServiceFactory.fs">
<Link>Completion\CompletionServiceFactory.fs</Link>
</Compile>
<Compile Include="ContentType.fs" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -83,6 +92,8 @@
<Reference Include="System" />
<Reference Include="PresentationCore" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="EnvDTE.dll" />
<Reference Include="EnvDTE80.dll" />
<Reference Include="Microsoft.VisualStudio.Threading, Version=$(RoslynVSBinariesVersion).0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>$(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
</Reference>
Expand Down
Loading