@@ -46,60 +46,19 @@ module private Patterns =
4646 else
4747 ValueNone
4848
49- /// Trim only spaces from the start if there is something else
50- /// before the open paren on the same line (or else we could move
51- /// the whole inner expression up a line); otherwise trim all whitespace
52- // from start and end.
53- let (| Trim |) ( span : TextSpan ) ( sourceText : SourceText ) =
54- let linePosition = sourceText.Lines.GetLinePosition span.Start
55- let line = ( sourceText.Lines.GetLineFromPosition span.Start) .ToString()
56-
57- if line.AsSpan( 0 , linePosition.Character) .LastIndexOfAnyExcept( ' ' , '(' ) >= 0 then
58- fun ( s : string ) -> s.TrimEnd() .TrimStart ' '
59- else
60- fun ( s : string ) -> s.Trim()
61-
62- /// Returns the offsides diff if the given span contains an expression
63- /// whose indentation would be made invalid if the open paren
64- /// were removed (because the offside line would be shifted), e.g.,
65- ///
66- /// // Valid.
67- /// (let x = 2
68- /// x)
69- ///
70- /// // Invalid.
71- /// ←let x = 2
72- /// x◌
73- ///
74- /// // Valid.
75- /// ◌let x = 2
76- /// x◌
77- [<return : Struct>]
78- let (| OffsidesDiff | _ |) ( span : TextSpan ) ( sourceText : SourceText ) =
79- let startLinePosition = sourceText.Lines.GetLinePosition span.Start
80- let endLinePosition = sourceText.Lines.GetLinePosition span.End
81- let startLineNo = startLinePosition.Line
82- let endLineNo = endLinePosition.Line
83-
84- if startLineNo = endLineNo then
85- ValueNone
86- else
87- let rec loop innerOffsides lineNo startCol =
88- if lineNo <= endLineNo then
89- let line = sourceText.Lines[ lineNo]. ToString()
49+ [<NoEquality; NoComparison>]
50+ type private InnerOffsides =
51+ /// We haven't found an inner construct yet.
52+ | NoneYet
9053
91- match line.AsSpan( startCol) .IndexOfAnyExcept( ' ' , ')' ) with
92- | - 1 -> loop innerOffsides ( lineNo + 1 ) 0
93- | i -> loop ( i + startCol) ( lineNo + 1 ) 0
94- else
95- ValueSome( startLinePosition.Character - innerOffsides)
54+ /// The start column of the first inner construct we find.
55+ /// This may not be on the same line as the open paren.
56+ | FirstLine of col : int
9657
97- loop startLinePosition.Character startLineNo ( startLinePosition.Character + 1 )
98-
99- let (| ShiftLeft | NoShift | ShiftRight |) n =
100- if n < 0 then ShiftLeft - n
101- elif n = 0 then NoShift
102- else ShiftRight n
58+ /// The leftmost start column of an inner construct on a line
59+ /// following the first inner construct we found.
60+ /// We keep the first column of the first inner construct for comparison at the end.
61+ | FollowingLine of firstLine : int * followingLine : int
10362
10463[<ExportCodeFixProvider( FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveUnnecessaryParentheses); Shared; Sealed>]
10564type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [<ImportingConstructor>] () =
@@ -147,22 +106,67 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [<ImportingConst
147106
148107 match firstChar, lastChar with
149108 | '(' , ')' ->
109+ /// Trim only spaces from the start if there is something else
110+ /// before the open paren on the same line (or else we could move
111+ /// the whole inner expression up a line); otherwise trim all whitespace
112+ /// from start and end.
113+ let (| Trim |) ( sourceText : SourceText ) =
114+ let linePosition = sourceText.Lines.GetLinePosition context.Span.Start
115+ let line = ( sourceText.Lines.GetLineFromPosition context.Span.Start). ToString()
116+
117+ if line.AsSpan( 0 , linePosition.Character). LastIndexOfAnyExcept( ' ' , '(' ) >= 0 then
118+ fun ( s : string ) -> s.TrimEnd() .TrimStart ' '
119+ else
120+ fun ( s : string ) -> s.Trim()
121+
122+ let (| ShiftLeft | NoShift | ShiftRight |) ( sourceText : SourceText ) =
123+ let startLinePosition = sourceText.Lines.GetLinePosition context.Span.Start
124+ let endLinePosition = sourceText.Lines.GetLinePosition context.Span.End
125+ let startLineNo = startLinePosition.Line
126+ let endLineNo = endLinePosition.Line
127+
128+ if startLineNo = endLineNo then
129+ NoShift
130+ else
131+ let outerOffsides = startLinePosition.Character
132+
133+ let rec loop innerOffsides lineNo startCol =
134+ if lineNo <= endLineNo then
135+ let line = sourceText.Lines[ lineNo]. ToString()
136+
137+ match line.AsSpan( startCol). IndexOfAnyExcept( ' ' , ')' ) with
138+ | - 1 -> loop innerOffsides ( lineNo + 1 ) 0
139+ | i ->
140+ match innerOffsides with
141+ | NoneYet -> loop ( FirstLine( i + startCol)) ( lineNo + 1 ) 0
142+ | FirstLine innerOffsides -> loop ( FollowingLine( innerOffsides, i + startCol)) ( lineNo + 1 ) 0
143+ | FollowingLine( firstLine, innerOffsides) ->
144+ loop ( FollowingLine( firstLine, min innerOffsides ( i + startCol))) ( lineNo + 1 ) 0
145+ else
146+ innerOffsides
147+
148+ match loop NoneYet startLineNo ( startLinePosition.Character + 1 ) with
149+ | NoneYet -> NoShift
150+ | FirstLine innerOffsides when innerOffsides < outerOffsides -> ShiftRight( outerOffsides - innerOffsides)
151+ | FirstLine innerOffsides -> ShiftLeft( innerOffsides - outerOffsides)
152+ | FollowingLine( firstLine, followingLine) ->
153+ match firstLine - outerOffsides with
154+ | 0 -> NoShift
155+ | 1 when firstLine < followingLine -> NoShift
156+ | primaryOffset when primaryOffset < 0 -> ShiftRight - primaryOffset
157+ | primaryOffset -> ShiftLeft primaryOffset
158+
150159 let adjusted =
151160 match sourceText with
152161 | TrailingOpen context.Span -> txt[ 1 .. txt.Length - 2 ]. TrimEnd()
153-
154- | Trim context.Span trim & OffsidesDiff context.Span spaces ->
155- match spaces with
156- | NoShift -> trim txt[ 1 .. txt.Length - 2 ]
157- | ShiftLeft spaces -> trim ( txt[ 1 .. txt.Length - 2 ]. Replace( " \n " + String( ' ' , spaces), " \n " ))
158- | ShiftRight spaces -> trim ( txt[ 1 .. txt.Length - 2 ]. Replace( " \n " , " \n " + String( ' ' , spaces)))
159-
160- | _ -> txt[ 1 .. txt.Length - 2 ]. Trim()
162+ | Trim trim & NoShift -> trim txt[ 1 .. txt.Length - 2 ]
163+ | Trim trim & ShiftLeft spaces -> trim ( txt[ 1 .. txt.Length - 2 ]. Replace( " \n " + String( ' ' , spaces), " \n " ))
164+ | Trim trim & ShiftRight spaces -> trim ( txt[ 1 .. txt.Length - 2 ]. Replace( " \n " , " \n " + String( ' ' , spaces)))
161165
162166 let newText =
163167 let (| ShouldPutSpaceBefore | _ |) ( s : string ) =
164- // " ……(……)"
165- // ↑↑ ↑
168+ // ……(……)
169+ // ↑↑ ↑
166170 match sourceText[ max ( context.Span.Start - 2 ) 0 ], sourceText[ max ( context.Span.Start - 1 ) 0 ], s[ 0 ] with
167171 | _, _, ( '\n' | '\r' ) -> None
168172 | '[' , '|' , ( Punctuation | LetterOrDigit) -> None
@@ -178,8 +182,8 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [<ImportingConst
178182 | _ -> None
179183
180184 let (| ShouldPutSpaceAfter | _ |) ( s : string ) =
181- // " (……)…"
182- // ↑ ↑
185+ // (……)…
186+ // ↑ ↑
183187 match s[ s.Length - 1 ], sourceText[ min context.Span.End ( sourceText.Length - 1 )] with
184188 | '>' , ( '|' | ']' ) -> Some ShouldPutSpaceAfter
185189 | _, ( ')' | ']' | '[' | '}' | '.' | ';' | ',' | '|' ) -> None
0 commit comments