-
Notifications
You must be signed in to change notification settings - Fork 789
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add editor formatting service to auto-deindent closing brackets (#3313)
* Add editor formatting service for auto-deindent * Minor refactor of the indentation service - do not indent after 'function' * Only use smart indentation if indent style is set to 'Smart' * Fix broken unit test build * Implement review comments, fix build * Fix some broken brace matching tests Still WIP, other tests still broken * Fix failing indentation tests * Add formatting service tests * Add more brace matching tests Fixes #2092
- Loading branch information
Showing
8 changed files
with
290 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// 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. | ||
|
||
namespace Microsoft.VisualStudio.FSharp.Editor | ||
|
||
open System.Composition | ||
open System.Collections.Generic | ||
|
||
open Microsoft.CodeAnalysis | ||
open Microsoft.CodeAnalysis.Editor | ||
open Microsoft.CodeAnalysis.Formatting | ||
open Microsoft.CodeAnalysis.Host.Mef | ||
open Microsoft.CodeAnalysis.Text | ||
|
||
open Microsoft.FSharp.Compiler.SourceCodeServices | ||
open System.Threading | ||
|
||
[<Shared>] | ||
[<ExportLanguageService(typeof<IEditorFormattingService>, FSharpConstants.FSharpLanguageName)>] | ||
type internal FSharpEditorFormattingService | ||
[<ImportingConstructor>] | ||
( | ||
checkerProvider: FSharpCheckerProvider, | ||
projectInfoManager: FSharpProjectOptionsManager | ||
) = | ||
|
||
static member GetFormattingChanges(documentId: DocumentId, sourceText: SourceText, filePath: string, checker: FSharpChecker, indentStyle: FormattingOptions.IndentStyle, projectOptions: FSharpProjectOptions option, position: int) = | ||
// Logic for determining formatting changes: | ||
// If first token on the current line is a closing brace, | ||
// match the indent with the indent on the line that opened it | ||
|
||
asyncMaybe { | ||
|
||
// Gate formatting on whether smart indentation is enabled | ||
// (this is what C# does) | ||
do! Option.guard (indentStyle = FormattingOptions.IndentStyle.Smart) | ||
|
||
let! projectOptions = projectOptions | ||
|
||
let line = sourceText.Lines.[sourceText.Lines.IndexOf position] | ||
|
||
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(filePath, projectOptions.OtherOptions |> List.ofArray) | ||
|
||
let tokens = Tokenizer.tokenizeLine(documentId, sourceText, line.Start, filePath, defines) | ||
|
||
let! firstMeaningfulToken = | ||
tokens | ||
|> List.tryFind (fun x -> | ||
x.Tag <> FSharpTokenTag.WHITESPACE && | ||
x.Tag <> FSharpTokenTag.COMMENT && | ||
x.Tag <> FSharpTokenTag.LINE_COMMENT) | ||
|
||
let! (left, right) = | ||
FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, filePath, projectOptions, position, "FormattingService") | ||
|
||
if right.StartColumn = firstMeaningfulToken.LeftColumn then | ||
// Replace the indentation on this line with the indentation of the left bracket | ||
let! leftSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, left) | ||
|
||
let indentChars (line : TextLine) = | ||
line.ToString() | ||
|> Seq.takeWhile ((=) ' ') | ||
|> Seq.length | ||
|
||
let startIndent = indentChars sourceText.Lines.[sourceText.Lines.IndexOf leftSpan.Start] | ||
let currentIndent = indentChars line | ||
|
||
return TextChange(TextSpan(line.Start, currentIndent), String.replicate startIndent " ") | ||
else | ||
return! None | ||
} | ||
|
||
member __.GetFormattingChangesAsync (document: Document, position: int, cancellationToken: CancellationToken) = | ||
async { | ||
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask | ||
let! options = document.GetOptionsAsync(cancellationToken) |> Async.AwaitTask | ||
let indentStyle = options.GetOption(FormattingOptions.SmartIndent, FSharpConstants.FSharpLanguageName) | ||
let projectOptionsOpt = projectInfoManager.TryGetOptionsForEditingDocumentOrProject document | ||
let! textChange = FSharpEditorFormattingService.GetFormattingChanges(document.Id, sourceText, document.FilePath, checkerProvider.Checker, indentStyle, projectOptionsOpt, position) | ||
|
||
return | ||
match textChange with | ||
| Some change -> | ||
ResizeArray([change]) :> IList<_> | ||
|
||
| None -> | ||
ResizeArray() :> IList<_> | ||
} | ||
|
||
interface IEditorFormattingService with | ||
member val SupportsFormatDocument = false | ||
member val SupportsFormatSelection = false | ||
member val SupportsFormatOnPaste = false | ||
member val SupportsFormatOnReturn = true | ||
|
||
override __.SupportsFormattingOnTypedCharacter (document, ch) = | ||
if FSharpIndentationService.IsSmartIndentEnabled document.Project.Solution.Workspace.Options then | ||
match ch with | ||
| ')' | ']' | '}' -> true | ||
| _ -> false | ||
else | ||
false | ||
|
||
override __.GetFormattingChangesAsync (_document, _span, cancellationToken) = | ||
async { return ResizeArray() :> IList<_> } | ||
|> RoslynHelpers.StartAsyncAsTask cancellationToken | ||
|
||
override __.GetFormattingChangesOnPasteAsync (_document, _span, cancellationToken) = | ||
async { return ResizeArray() :> IList<_> } | ||
|> RoslynHelpers.StartAsyncAsTask cancellationToken | ||
|
||
override this.GetFormattingChangesAsync (document, _typedChar, position, cancellationToken) = | ||
this.GetFormattingChangesAsync (document, position, cancellationToken) | ||
|> RoslynHelpers.StartAsyncAsTask cancellationToken | ||
|
||
override this.GetFormattingChangesOnReturnAsync (document, position, cancellationToken) = | ||
this.GetFormattingChangesAsync (document, position, cancellationToken) | ||
|> RoslynHelpers.StartAsyncAsTask cancellationToken |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.