@@ -15,27 +15,33 @@ open System.Text.RegularExpressions
1515open System.IO
1616
1717module internal FileSystemCompletion =
18- let [<Literal>] NetworkPath = " \\\\ "
19- let commitRules = ImmutableArray.Create( CharacterSetModificationRule.Create( CharacterSetModificationKind.Replace, '"' , '\\' , ',' , '/' ))
20- let rules = CompletionItemRules.Create( commitCharacterRules= commitRules)
18+ let [<Literal>] private NetworkPath = " \\\\ "
19+ let private commitRules = ImmutableArray.Create( CharacterSetModificationRule.Create( CharacterSetModificationKind.Replace, '"' , '\\' , ',' , '/' ))
20+ let private rules = CompletionItemRules.Create( commitCharacterRules= commitRules)
2121
22- let getQuotedPathStart ( text : SourceText , position : int , quotedPathGroup : Group ) =
22+ let private getQuotedPathStart ( text : SourceText , position : int , quotedPathGroup : Group ) =
2323 text.Lines.GetLineFromPosition( position) .Start+ quotedPathGroup.Index
2424
25- let getPathThroughLastSlash ( text : SourceText , position : int , quotedPathGroup : Group ) =
25+ let private getPathThroughLastSlash ( text : SourceText , position : int , quotedPathGroup : Group ) =
2626 PathCompletionUtilities.GetPathThroughLastSlash(
2727 quotedPath= quotedPathGroup.Value,
2828 quotedPathStart= getQuotedPathStart( text, position, quotedPathGroup),
2929 position= position)
3030
31- let getTextChangeSpan ( text : SourceText , position : int , quotedPathGroup : Group ) =
31+ let private getTextChangeSpan ( text : SourceText , position : int , quotedPathGroup : Group ) =
3232 PathCompletionUtilities.GetTextChangeSpan(
3333 quotedPath= quotedPathGroup.Value,
3434 quotedPathStart= getQuotedPathStart( text, position, quotedPathGroup),
3535 position= position)
3636
37- let getItems ( provider : CompletionProvider , document : Document , position : int , allowableExtensions : string [], directiveRegex : Regex ) =
37+ let private getFileGlyph ( extention : string ) =
38+ match extentionwith
39+ | " .exe" | " .dll" -> Some Glyph.Assembly
40+ | _ -> None
41+
42+ let getItems ( provider : CompletionProvider , document : Document , position : int , allowableExtensions : string list , directiveRegex : Regex , searchPaths : string list ) =
3843asyncMaybe {
44+ do ! Option.guard( Path.GetExtension document.FilePath= " .fsx" )
3945let! ct = liftAsync Async.CancellationToken
4046let! text = document.GetTextAsync ct
4147let line = text.Lines.GetLineFromPosition( position)
@@ -53,16 +59,14 @@ module internal FileSystemCompletion =
5359do ! Option.guard( not ( isNull snapshot))
5460let fileSystem = CurrentWorkingDirectoryDiscoveryService.GetService( snapshot)
5561
56- let searchPaths = ImmutableArray.Create( Path.GetDirectoryName document.FilePath)
57-
5862let helper =
5963 FileSystemCompletionHelper(
6064 provider,
6165 getTextChangeSpan( text, position, quotedPathGroup),
6266 fileSystem,
6367 Glyph.OpenFolder,
64- Glyph.None,
65- searchPaths= searchPaths,
68+ allowableExtensions |> List.tryPick getFileGlyph |> Option.defaultValue Glyph.None,
69+ searchPaths= Seq.toImmutableArray searchPaths,
6670 allowableExtensions= allowableExtensions,
6771 itemRules= rules)
6872
@@ -89,38 +93,61 @@ module internal FileSystemCompletion =
8993else
9094 None
9195
92- type internal LoadDirectiveCompletionProvider () =
96+ let private includeDirectiveCleanRegex = Regex( """ #I\s+(@?"*(?<literal>[^"]*)"?)""" , RegexOptions.Compiled||| RegexOptions.ExplicitCapture)
97+
98+ let getIncludeDirectives ( document : Document , position : int ) =
99+ async {
100+ let! ct = Async.CancellationToken
101+ let! text = document.GetTextAsync( ct)
102+ let lines = text.Lines
103+ let caretLine = text.Lines.GetLinePosition( position). Line
104+ return
105+ lines
106+ |> Seq.filter( fun x -> x.LineNumber<= caretLine)
107+ |> Seq.choose( fun line ->
108+ let lineStr = line.ToString() .Trim()
109+ // optimization: fail fast if the line does not start with "(optional spaces) #I"
110+ if not ( lineStr.StartsWith" #I" ) then None
111+ else
112+ match includeDirectiveCleanRegex.Match lineStrwith
113+ | mwhen m.Success-> Some( m.Groups.[ " literal" ]. Value)
114+ | _ -> None
115+ )
116+ |> Seq.toList
117+ }
118+
119+ [<AbstractClass>]
120+ type internal HashDirectiveCompletionProvider ( directiveRegex : string , allowableExtensions : string list , useIncludeDirectives : bool ) =
93121inherit CommonCompletionProvider()
94122
95- let directiveRegex = Regex( """ #load\s+(@?"*(?<literal>"[^"]*"?)) """ , RegexOptions.Compiled||| RegexOptions.ExplicitCapture)
123+ let directiveRegex = Regex( directiveRegex , RegexOptions.Compiled||| RegexOptions.ExplicitCapture)
96124
97125override this.ProvideCompletionsAsync ( context ) =
98126async {
99- let! items = FileSystemCompletion.getItems( this, context.Document, context.Position, [| " .fs" ; " .fsx" |], directiveRegex)
127+ let defaultSearchPath = Path.GetDirectoryName context.Document.FilePath
128+ let! extraSearchPaths =
129+ if useIncludeDirectivesthen
130+ FileSystemCompletion.getIncludeDirectives( context.Document, context.Position)
131+ else async.Return[]
132+ let searchPaths = defaultSearchPath:: extraSearchPaths
133+ let! items = FileSystemCompletion.getItems( this, context.Document, context.Position, allowableExtensions, directiveRegex, searchPaths)
100134 context.AddItems( items)
101135} |> CommonRoslynHelpers.StartAsyncUnitAsTask context.CancellationToken
102136
103- override __.IsInsertionTrigger ( text , position , _options ) = FileSystemCompletion.isInsertionTrigger( text, position)
137+ override __.IsInsertionTrigger ( text , position , _ ) = FileSystemCompletion.isInsertionTrigger( text, position)
104138
105139override __.GetTextChangeAsync ( selectedItem , ch , cancellationToken ) =
106140match FileSystemCompletion.getTextChange( selectedItem, ch) with
107141| Some x-> Task.FromResult( Nullable x)
108142| None-> base .GetTextChangeAsync( selectedItem, ch, cancellationToken)
109143
144+
145+ type internal LoadDirectiveCompletionProvider () =
146+ inherit HashDirectiveCompletionProvider( """ \s*#load\s+(@?"*(?<literal>"[^"]*"?))""" , [ " .fs" ; " .fsx" ], useIncludeDirectives= true )
147+
110148type internal ReferenceDirectiveCompletionProvider () =
111- inherit CommonCompletionProvider ( )
149+ inherit HashDirectiveCompletionProvider ( """ \s*#r\s+(@?"*(?<literal>"[^"]*"?)) """ , [ " .dll " ; " .exe " ], useIncludeDirectives = true )
112150
113- let directiveRegex = Regex( """ #r\s+(@?"*(?<literal>"[^"]*"?))""" , RegexOptions.Compiled||| RegexOptions.ExplicitCapture)
114-
115- override this.ProvideCompletionsAsync ( context ) =
116- async {
117- let! items = FileSystemCompletion.getItems( this, context.Document, context.Position, [| " .dll" ; " .exe" |], directiveRegex)
118- context.AddItems( items)
119- } |> CommonRoslynHelpers.StartAsyncUnitAsTask context.CancellationToken
120-
121- override __.IsInsertionTrigger ( text , position , _options ) = FileSystemCompletion.isInsertionTrigger( text, position)
122-
123- override __.GetTextChangeAsync ( selectedItem , ch , cancellationToken ) =
124- match FileSystemCompletion.getTextChange( selectedItem, ch) with
125- | Some x-> Task.FromResult( Nullable x)
126- | None-> base .GetTextChangeAsync( selectedItem, ch, cancellationToken)
151+ type internal IncludeDirectiveCompletionProvider () =
152+ // we have to pass an extension that's not met in real life because if we pass empty list, it does not filter at all.
153+ inherit HashDirectiveCompletionProvider( """ \s*#I\s+(@?"*(?<literal>"[^"]*"?))""" , [ " .impossible_extension" ], useIncludeDirectives= false )