Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open Microsoft.CodeAnalysis
open Microsoft.VisualStudio
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop

/// Helpers for determining whether a Roslyn Document corresponds to the document
/// currently active (focused) in the Visual Studio shell.
///
/// Background expensive analyzers (UnusedOpens, UnusedDeclarations, SimplifyName,
/// InlayHints) should run only for the active document, mirroring how C# restricts
/// "remove unnecessary usings" and similar live analyzers.
///
/// Roslyn's BackgroundAnalysisScope lets a host choose "open documents" or
/// "entire solution" but has no built-in "active document only" tier, so we
/// determine the truly active document ourselves via the VS shell.
[<RequireQualifiedAccess>]
module internal ActiveDocumentDetection =

/// Returns the document moniker (full file path) of the currently focused
/// editor window, or ValueNone if it cannot be determined.
let tryGetActiveDocumentMoniker (serviceProvider: IServiceProvider) : string voption =
match serviceProvider.GetService(typeof<SVsShellMonitorSelection>) with
| :? IVsMonitorSelection as monitorSelection ->
let mutable frameObj = null

if
ErrorHandler.Succeeded(
monitorSelection.GetCurrentElementValue(uint32 VSConstants.VSSELELEMID.SEID_DocumentFrame, &frameObj)
)
then
match frameObj with
| :? IVsWindowFrame as frame ->
let mutable monikerObj = null

if
ErrorHandler.Succeeded(frame.GetProperty(int32 __VSFPROPID.VSFPROPID_pszMkDocument, &monikerObj))
then
match monikerObj with
| :? string as moniker -> ValueSome moniker
| _ -> ValueNone
else
ValueNone
| _ -> ValueNone
else
ValueNone
| _ -> ValueNone

/// Returns true when the given document is the currently active editor document.
///
/// Falls back to true (= do not suppress analysis) when the active document
/// cannot be determined, so analysis is never silently lost.
let isActiveDocument (serviceProvider: IServiceProvider) (document: Document) : bool =
match document.FilePath with
| null -> true
| filePath ->
match tryGetActiveDocumentMoniker serviceProvider with
| ValueNone -> true // couldn't determine the active document, don't suppress analysis
| ValueSome activeMoniker -> String.Equals(activeMoniker, filePath, StringComparison.OrdinalIgnoreCase)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ type private PerDocumentSavedData =
}

[<Export(typeof<IFSharpSimplifyNameDiagnosticAnalyzer>)>]
type internal SimplifyNameDiagnosticAnalyzer [<ImportingConstructor>] () =
type internal SimplifyNameDiagnosticAnalyzer
[<ImportingConstructor>]
([<Import("Microsoft.VisualStudio.Shell.SVsServiceProvider")>] serviceProvider: IServiceProvider) =

static let userOpName = "SimplifyNameDiagnosticAnalyzer"
static let cache = new MemoryCache("FSharp.Editor." + userOpName)
Expand All @@ -37,6 +39,7 @@ type internal SimplifyNameDiagnosticAnalyzer [<ImportingConstructor>] () =

asyncMaybe {
do! Option.guard document.Project.IsFSharpCodeFixesSimplifyNameEnabled
do! Option.guard (ActiveDocumentDetection.isActiveDocument serviceProvider document)
do Trace.TraceInformation("{0:n3} (start) SimplifyName", DateTime.Now.TimeOfDay.TotalSeconds)
let! textVersion = document.GetTextVersionAsync(cancellationToken)
let textVersionHash = textVersion.GetHashCode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
open CancellableTasks

[<Export(typeof<IFSharpUnusedDeclarationsDiagnosticAnalyzer>)>]
type internal UnusedDeclarationsAnalyzer [<ImportingConstructor>] () =
type internal UnusedDeclarationsAnalyzer
[<ImportingConstructor>]
([<Import("Microsoft.VisualStudio.Shell.SVsServiceProvider")>] serviceProvider: IServiceProvider) =

interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with

member _.AnalyzeSemanticsAsync(descriptor, document, cancellationToken) =
if
(document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript)
|| not document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled
|| not (ActiveDocumentDetection.isActiveDocument serviceProvider document)
then
Threading.Tasks.Task.FromResult(ImmutableArray.Empty)
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
open CancellableTasks

[<Export(typeof<IFSharpUnusedOpensDiagnosticAnalyzer>)>]
type internal UnusedOpensDiagnosticAnalyzer [<ImportingConstructor>] () =
type internal UnusedOpensDiagnosticAnalyzer
[<ImportingConstructor>]
([<Import("Microsoft.VisualStudio.Shell.SVsServiceProvider")>] serviceProvider: IServiceProvider) =

static member GetUnusedOpenRanges(document: Document) =
cancellableTask {
Expand All @@ -41,7 +43,10 @@ type internal UnusedOpensDiagnosticAnalyzer [<ImportingConstructor>] () =
interface IFSharpUnusedOpensDiagnosticAnalyzer with

member _.AnalyzeSemanticsAsync(descriptor, document: Document, cancellationToken: CancellationToken) =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then
if
(document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript)
|| not (ActiveDocumentDetection.isActiveDocument serviceProvider document)
then
Tasks.Task.FromResult(ImmutableArray.Empty)
else
cancellableTask {
Expand Down
1 change: 1 addition & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="Formatting\EditorFormattingService.fs" />
<Compile Include="Debugging\BreakpointResolutionService.fs" />
<Compile Include="Debugging\LanguageDebugInfoService.fs" />
<Compile Include="Diagnostics\ActiveDocumentDetection.fs" />
<Compile Include="Diagnostics\UnnecessaryParenthesesDiagnosticAnalyzer.fs" />
<Compile Include="Diagnostics\DocumentDiagnosticAnalyzer.fs" />
<Compile Include="Diagnostics\SimplifyNameDiagnosticAnalyzer.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Microsoft.VisualStudio.FSharp.Editor.Hints

open System
open System.Collections.Immutable
open System.ComponentModel.Composition
open System.Threading.Tasks
Expand All @@ -15,7 +16,10 @@ open CancellableTasks
// e.g. signature hints above the line, pipeline hints on the side and so on.

[<Export(typeof<IFSharpInlineHintsService2>)>]
type internal FSharpInlayHintsService [<ImportingConstructor>] (settings: EditorOptions) =
type internal FSharpInlayHintsService
[<ImportingConstructor>]
(settings: EditorOptions,
[<Import("Microsoft.VisualStudio.Shell.SVsServiceProvider")>] serviceProvider: IServiceProvider) =

static let userOpName = "Hints"

Expand All @@ -27,7 +31,7 @@ type internal FSharpInlayHintsService [<ImportingConstructor>] (settings: Editor
else
OptionParser.getHintKinds settings.Advanced

if hintKinds.IsEmpty then
if hintKinds.IsEmpty || not (ActiveDocumentDetection.isActiveDocument serviceProvider document) then
Task.FromResult ImmutableArray.Empty
else
cancellableTask {
Expand Down
Loading