1+ // Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel .Composition
7+ open Microsoft.VisualStudio
8+ open Microsoft.VisualStudio .FSharp .LanguageService
9+ open Microsoft.VisualStudio .FSharp .Shared
10+ open Microsoft.VisualStudio .Editor
11+ open Microsoft.VisualStudio .Shell
12+ open Microsoft.VisualStudio .Shell .Interop
13+ open Microsoft.VisualStudio .Text
14+ open Microsoft.VisualStudio .Text .BraceCompletion
15+ open Microsoft.VisualStudio .Text .Editor
16+ open Microsoft.VisualStudio .TextManager .Interop
17+ open Microsoft.VisualStudio .Utilities
18+
19+ /// F# brace completion context implementation
20+ type internal CompletionContext ( tokenContext : TokenContext , textManager : IVsTextManager ) =
21+ interface IBraceCompletionContextwith
22+ member this.Start ( _ ) =
23+ ()
24+
25+ /// Called when user hits Return while inside of a brace completion session.
26+ member this.OnReturn ( session ) =
27+
28+ let lp = [| LANGPREFERENCES( guidLang= Guid( FSharpCommonConstants.languageServiceGuidString)) |]
29+ ErrorHandler.ThrowOnFailure( textManager.GetUserPreferences( null , null , lp, null )) |> ignore
30+
31+ // if smart indent is not enabled, or we are in a string, don't do any special formatting
32+ if ( lp.[ 0 ]. IndentStyle<> vsIndentStyle.vsIndentStyleSmart|| session.OpeningBrace= '"' ) then
33+ ()
34+ else
35+ // within other braces:
36+ // leave the opening brace where it is
37+ // put the caret one line down, indented once from the previous line
38+ // put the closing brace one line below that, justified with the original line
39+ // let q = query {
40+ // $caret$
41+ // }
42+
43+ let openingBraceLine = session.OpeningPoint.GetPoint( session.SubjectBuffer.CurrentSnapshot) .GetContainingLine()
44+ let existingIndent = TextHelper.physicalIndentStringOfLine session.TextView openingBraceLine
45+ let caretPosition = session.TextView.Caret.Position.BufferPosition.Position
46+ let singleIndent = TextHelper.singleIndentString session.TextView
47+
48+ // we will already have "existing" indentation applied, justifying the caret with the previous line.
49+ // insert an additional indent, a newline, then another buffer matching "existing" indentation
50+ use edit= session.SubjectBuffer.CreateEdit()
51+ if edit.Insert( caretPosition, String.Format( " {0}{1}{2}" , singleIndent, Environment.NewLine, existingIndent)) then
52+ let newSnapshot = edit.Apply()
53+
54+ // if edit failed to apply, snapshots will be the same
55+ if newSnapshot= edit.Snapshotthen
56+ ()
57+ else
58+ session.TextView.Caret.MoveTo(
59+ SnapshotPoint( newSnapshot, caretPosition+ singleIndent.Length) .TranslateTo( session.TextView.TextSnapshot, PointTrackingMode.Positive)
60+ ) |> ignore
61+
62+ /// Called when user types the (potential) closing brace for a session.
63+ /// Return false if we don't think user is really completing the session. E.g. escaped \" should not close a string session
64+ member this.AllowOverType ( session ) =
65+ let caretPosition = session.TextView.Caret.Position.BufferPosition.Position
66+ let line = session.TextView.TextSnapshot.GetLineFromPosition( caretPosition)
67+ let lineNum = line.LineNumber
68+ let braceColumn = caretPosition- line.Start.Position
69+
70+ // test what state we'd be in if the closing brace and a space were typed
71+ let tokenInfo = tokenContext.GetContextAt( session.TextView, lineNum, braceColumn+ 1 , ( string session.ClosingBrace+ " " ), braceColumn)
72+
73+ match tokenInfo.Typewith
74+ | TokenType.Comment
75+ | TokenType.Unknown
76+ | TokenType.String-> false
77+ | _ -> true
78+
79+ member this.Finish ( _ ) =
80+ ()
81+
82+ [<Export( typeof< IBraceCompletionContextProvider>) >]
83+ [<BracePair( '"' , '"' ) >]
84+ [<BracePair( '[' , ']' ) >]
85+ [<BracePair( '{' , '}' ) >]
86+ [<BracePair( '(' , ')' ) >]
87+ [<ContentType( " F#" ) >]
88+ type BraceCompletionContextProvider [<ImportingConstructor>] ( serviceProvider : SVsServiceProvider , adapterService : IVsEditorAdaptersFactoryService ) =
89+
90+ let tokenContext = TokenContext( serviceProvider, adapterService)
91+ let textManager = serviceProvider.GetService( typeof< SVsTextManager>) :?> IVsTextManager
92+
93+ interface IBraceCompletionContextProviderwith
94+
95+ /// Called when user types a character matching a supported opening brace
96+ member this.TryCreateContext ( textView : ITextView , snap : SnapshotPoint , openingBrace : char , _ : char , completionContext : byref < IBraceCompletionContext >) =
97+ if textView= null then raise( ArgumentException( " textView" )) else
98+
99+ let line = snap.GetContainingLine()
100+ let lineNum = line.LineNumber
101+ let braceColumn = snap.Position- line.Start.Position
102+
103+ // check what token context the user is at to decide if we should do brace completion
104+ let info = tokenContext.GetContextAt( textView, lineNum, braceColumn, " " , braceColumn)
105+ match info.Typewith
106+ | TokenType.String
107+ | TokenType.Comment
108+ | TokenType.Unknown->
109+ // don't initiate brace completion inside of strings, comments, or inactive code
110+ completionContext<- null
111+ false
112+ | _ ->
113+ // check if we might be in a char literal
114+ let prevChar =
115+ match snap.Position- 1 with
116+ | pwhen p< 0 -> None
117+ | p-> Some( snap.Snapshot.[ p])
118+
119+ match prevCharwith
120+ | Some( '\'' ) ->
121+ // previous character was a ', but could this be a char literal?
122+ // test the token context in the case that we added a closing ' after the brace character
123+ let ( insideCharLit , outsideCharLit ) =
124+ ( tokenContext.GetContextAt( textView, lineNum, braceColumn, ( string openingBrace) + " '" , braceColumn) .Type,
125+ tokenContext.GetContextAt( textView, lineNum, braceColumn+ 2 , ( string openingBrace) + " '" , braceColumn) .Type)
126+
127+ match ( insideCharLit, outsideCharLit) with
128+ | ( TokenType.String, t) when t<> TokenType.String->
129+ completionContext<- null
130+ false
131+ | _ ->
132+ completionContext<- CompletionContext( tokenContext, textManager)
133+ true
134+ | _ ->
135+ completionContext<- CompletionContext( tokenContext, textManager)
136+ true