<commit_before>﻿namespace FSharpVSPowerTools.Navigation

open System
open System.Threading
open System.IO
open System.Windows
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Text.Operations
open Microsoft.VisualStudio
open Microsoft.VisualStudio.OLE.Interop
open Microsoft.VisualStudio.Shell.Interop
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range
open FSharpVSPowerTools
open FSharpVSPowerTools.ProjectSystem
open FSharp.CompilerBinding

[<RequireQualifiedAccess>]
module PkgCmdConst =
    let cmdidFindReferences = uint32 VSConstants.VSStd97CmdID.FindReferences
    let guidBuiltinCmdSet = VSConstants.GUID_VSStandardCommandSet97
    let guidSymbolLibrary = Guid("2ad4e2a2-b89f-48b6-98e8-363bd1a35450")

[<NoEquality; NoComparison>]
type DocumentState =
    { Word: SnapshotSpan * Symbol
      File: string
      Project: IProjectProvider
      TargetProjects: IProjectProvider list }

type FindReferencesFilter(view: IWpfTextView, vsLanguageService: VSLanguageService, serviceProvider: System.IServiceProvider) =
    let getDocumentState() =
        async {
            let dte = serviceProvider.GetService<EnvDTE.DTE, SDTE>()
            let projectItems = maybe {
                let! caretPos = view.TextBuffer.GetSnapshotPoint view.Caret.Position
                let! doc = dte.GetActiveDocument()
                let! project = ProjectProvider.createForDocument doc
                let! span, sym = vsLanguageService.GetSymbol(caretPos, project)
                return doc.FullName, project, span, sym }

            match projectItems with
            | Some (file, project, span, sym) ->
                let! symbolUse = vsLanguageService.GetFSharpSymbolUse(span, sym, file, project, AllowStaleResults.No)
                match symbolUse with
                | Some (symbolUse, _) ->
                    match ProjectProvider.getSymbolUsageScope symbolUse.Symbol dte file with
                    | Some symbolDeclLocation ->
                        let dependentProjects =
                            let declProject = 
                                match symbolDeclLocation with
                                | SymbolDeclarationLocation.File -> project
                                | SymbolDeclarationLocation.Project p -> p

                            let dependentProjects = ProjectProvider.getDependentProjects dte declProject
                            if dependentProjects |> List.exists (fun x -> x.ProjectFileName = declProject.ProjectFileName)
                            then dependentProjects
                            else declProject :: dependentProjects

                        return Some { Word = span, sym
                                      File = file
                                      Project = project
                                      TargetProjects = dependentProjects }
                    | _ -> return None
                | _ -> return None
            | _ -> return None
        }

    let findReferences() = 
        async {
            let! state = getDocumentState()
            match state with
            | Some { Word = cw, sym; File = file; Project = project; TargetProjects = targetProjects } ->
                let! references = vsLanguageService.FindUsages (cw, file, project, targetProjects) 
                let references = 
                    references
                    |> Option.map (fun (_, _, refs) -> 
                        refs 
                        |> Seq.map (fun symbolUse -> (symbolUse.FileName, symbolUse))
                        |> Seq.groupBy (fst >> Path.GetFullPath)
                        |> Seq.map (fun (_, symbolUses) -> 
                            // Sort symbols by positions
                            symbolUses 
                            |> Seq.map snd 
                            |> Seq.sortBy (fun s -> s.RangeAlternate.StartLine, s.RangeAlternate.StartColumn))
                        |> Seq.concat)
                    |> fun opt -> defaultArg opt Seq.empty
            
                let findResults = FSharpLibraryNode("Find Symbol Results", serviceProvider)
                for r in references do<commit_msg>FindReferenceFilter searches all over the solution for externally declared symbol usages<commit_after>﻿namespace FSharpVSPowerTools.Navigation

open System
open System.Threading
open System.IO
open System.Windows
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Text.Operations
open Microsoft.VisualStudio
open Microsoft.VisualStudio.OLE.Interop
open Microsoft.VisualStudio.Shell.Interop
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range
open FSharpVSPowerTools
open FSharpVSPowerTools.ProjectSystem
open FSharp.CompilerBinding

[<RequireQualifiedAccess>]
module PkgCmdConst =
    let cmdidFindReferences = uint32 VSConstants.VSStd97CmdID.FindReferences
    let guidBuiltinCmdSet = VSConstants.GUID_VSStandardCommandSet97
    let guidSymbolLibrary = Guid("2ad4e2a2-b89f-48b6-98e8-363bd1a35450")

[<NoEquality; NoComparison>]
type DocumentState =
    { Word: SnapshotSpan * Symbol
      File: string
      Project: IProjectProvider
      TargetProjects: IProjectProvider list }

type FindReferencesFilter(view: IWpfTextView, vsLanguageService: VSLanguageService, serviceProvider: System.IServiceProvider) =
    let getDocumentState() =
        async {
            let dte = serviceProvider.GetService<EnvDTE.DTE, SDTE>()
            let projectItems = maybe {
                let! caretPos = view.TextBuffer.GetSnapshotPoint view.Caret.Position
                let! doc = dte.GetActiveDocument()
                let! project = ProjectProvider.createForDocument doc
                let! span, sym = vsLanguageService.GetSymbol(caretPos, project)
                return doc.FullName, project, span, sym }

            match projectItems with
            | Some (file, project, span, sym) ->
                let! symbolUse = vsLanguageService.GetFSharpSymbolUse(span, sym, file, project, AllowStaleResults.No)
                match symbolUse with
                | Some (symbolUse, _) ->
                    let targetProjects =
                        match ProjectProvider.getSymbolUsageScope symbolUse.Symbol dte file with
                        | Some symbolDeclLocation ->
                            let declProject = 
                                match symbolDeclLocation with
                                | SymbolDeclarationLocation.File -> project
                                | SymbolDeclarationLocation.Project p -> p

                            let dependentProjects = ProjectProvider.getDependentProjects dte declProject
                            if dependentProjects |> List.exists (fun x -> x.ProjectFileName = declProject.ProjectFileName)
                            then dependentProjects
                            else declProject :: dependentProjects
                        // The symbol is declared in .NET framework, an external assembly or in a C# project withing the solution.
                        // In order to find all its usages we have to check all F# projects.
                        | None -> dte.ListFSharpProjectsInSolution() |> List.map ProjectProvider.createForProject
    
                    return Some { Word = span, sym
                                  File = file
                                  Project = project
                                  TargetProjects = targetProjects }
                | _ -> return None
            | _ -> return None
        }

    let findReferences() = 
        async {
            let! state = getDocumentState()
            match state with
            | Some { Word = cw, sym; File = file; Project = project; TargetProjects = targetProjects } ->
                let! references = vsLanguageService.FindUsages (cw, file, project, targetProjects) 
                let references = 
                    references
                    |> Option.map (fun (_, _, refs) -> 
                        refs 
                        |> Seq.map (fun symbolUse -> (symbolUse.FileName, symbolUse))
                        |> Seq.groupBy (fst >> Path.GetFullPath)
                        |> Seq.map (fun (_, symbolUses) -> 
                            // Sort symbols by positions
                            symbolUses 
                            |> Seq.map snd 
                            |> Seq.sortBy (fun s -> s.RangeAlternate.StartLine, s.RangeAlternate.StartColumn))
                        |> Seq.concat)
                    |> fun opt -> defaultArg opt Seq.empty
            
                let findResults = FSharpLibraryNode("Find Symbol Results", serviceProvider)
                for r in references do
{'fraction_non_alphanumeric': 0.06617480136208853, 'fraction_numerical': 0.0061293984108967085, 'mean_word_length': 1.7253325085060316, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1211863', 'n_tokens_mistral': 2193, 'n_tokens_neox': 2017, 'n_words': 531}