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.Collections .Immutable
7+ open System.Threading
8+ open System.Threading .Tasks
9+ open Microsoft.CodeAnalysis
10+ open Microsoft.CodeAnalysis .Completion
11+ open Microsoft.CodeAnalysis .Editor .Implementation .IntelliSense .Completion .FileSystem
12+ open Microsoft.CodeAnalysis .Text
13+
14+ open System.Text .RegularExpressions
15+ open System.IO
16+
17+ module internal FileSystemCompletion =
18+ let [<Literal>] NetworkPath = " \\\\ "
19+ let commitRules = ImmutableArray.Create( CharacterSetModificationRule.Create( CharacterSetModificationKind.Replace, '"' , '\\' , ',' , '/' ))
20+ let rules = CompletionItemRules.Create( commitCharacterRules= commitRules)
21+
22+ let getQuotedPathStart ( text : SourceText , position : int , quotedPathGroup : Group ) =
23+ text.Lines.GetLineFromPosition( position) .Start+ quotedPathGroup.Index
24+
25+ let getPathThroughLastSlash ( text : SourceText , position : int , quotedPathGroup : Group ) =
26+ PathCompletionUtilities.GetPathThroughLastSlash(
27+ quotedPath= quotedPathGroup.Value,
28+ quotedPathStart= getQuotedPathStart( text, position, quotedPathGroup),
29+ position= position)
30+
31+ let getTextChangeSpan ( text : SourceText , position : int , quotedPathGroup : Group ) =
32+ PathCompletionUtilities.GetTextChangeSpan(
33+ quotedPath= quotedPathGroup.Value,
34+ quotedPathStart= getQuotedPathStart( text, position, quotedPathGroup),
35+ position= position)
36+
37+ let getItems ( provider : CompletionProvider , document : Document , position : int , allowableExtensions : string [], directiveRegex : Regex ) =
38+ asyncMaybe {
39+ let! ct = liftAsync Async.CancellationToken
40+ let! text = document.GetTextAsync ct
41+ let line = text.Lines.GetLineFromPosition( position)
42+ let lineText = text.ToString( TextSpan.FromBounds( line.Start, position));
43+ let m = directiveRegex.Match lineText
44+
45+ do ! Option.guard m.Success
46+ let quotedPathGroup = m.Groups.[ " literal" ]
47+ let quotedPath = quotedPathGroup.Value;
48+ let endsWithQuote = PathCompletionUtilities.EndsWithQuote( quotedPath)
49+
50+ do ! Option.guard( not ( endsWithQuote&& ( position>= line.Start+ m.Length)))
51+ let snapshot = text.FindCorrespondingEditorTextSnapshot()
52+
53+ do ! Option.guard( not ( isNull snapshot))
54+ let fileSystem = CurrentWorkingDirectoryDiscoveryService.GetService( snapshot)
55+
56+ let searchPaths = ImmutableArray.Create( Path.GetDirectoryName document.FilePath)
57+
58+ let helper =
59+ FileSystemCompletionHelper(
60+ provider,
61+ getTextChangeSpan( text, position, quotedPathGroup),
62+ fileSystem,
63+ Glyph.OpenFolder,
64+ Glyph.None,
65+ searchPaths= searchPaths,
66+ allowableExtensions= allowableExtensions,
67+ itemRules= rules)
68+
69+ let pathThroughLastSlash = getPathThroughLastSlash( text, position, quotedPathGroup)
70+ let documentPath = if document.Project.IsSubmissionthen null else document.FilePath
71+ return helper.GetItems( pathThroughLastSlash, documentPath)
72+ } |> Async.map( Option.defaultValue ImmutableArray.Empty)
73+
74+ let isInsertionTrigger ( text : SourceText , position ) =
75+ // Bring up completion when the user types a quote (i.e.: #r "), or if they type a slash
76+ // path separator character, or if they type a comma (#r "foo,version...").
77+ // Also, if they're starting a word. i.e. #r "c:\W
78+ let ch = text.[ position]
79+ ch= '"' || ch= '\\' || ch= ',' || ch= '/' ||
80+ CommonCompletionUtilities.IsStartingNewWord( text, position, ( fun x -> Char.IsLetter x), ( fun x -> Char.IsLetterOrDigit x))
81+
82+ let getTextChange ( selectedItem : CompletionItem , ch : Nullable < char >) =
83+ // When we commit "\\" when the user types \ we have to adjust for the fact that the
84+ // controller will automatically append \ after we commit. Because of that, we don't
85+ // want to actually commit "\\" as we'll end up with "\\\". So instead we just commit
86+ // "\" and know that controller will append "\" and give us "\\".
87+ if selectedItem.DisplayText= NetworkPath&& ch= Nullable'\\' then
88+ Some( TextChange( selectedItem.Span, " \\ " ))
89+ else
90+ None
91+
92+ type internal LoadDirectiveCompletionProvider () =
93+ inherit CommonCompletionProvider()
94+
95+ let directiveRegex = Regex( """ #load\s+(@?"*(?<literal>"[^"]*"?))""" , RegexOptions.Compiled||| RegexOptions.ExplicitCapture)
96+
97+ override this.ProvideCompletionsAsync ( context ) =
98+ async {
99+ let! items = FileSystemCompletion.getItems( this, context.Document, context.Position, [| " .fs" ; " .fsx" |], directiveRegex)
100+ context.AddItems( items)
101+ } |> CommonRoslynHelpers.StartAsyncUnitAsTask context.CancellationToken
102+
103+ override __.IsInsertionTrigger ( text , position , _options ) = FileSystemCompletion.isInsertionTrigger( text, position)
104+
105+ override __.GetTextChangeAsync ( selectedItem , ch , cancellationToken ) =
106+ match FileSystemCompletion.getTextChange( selectedItem, ch) with
107+ | Some x-> Task.FromResult( Nullable x)
108+ | None-> base .GetTextChangeAsync( selectedItem, ch, cancellationToken)
109+
110+ type internal ReferenceDirectiveCompletionProvider () =
111+ inherit CommonCompletionProvider()
112+
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)