-
Notifications
You must be signed in to change notification settings - Fork 804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add editor formatting service to auto-deindent closing brackets #3313
Changes from 1 commit
9588eb3
bbf8eae
a7784be
7661490
b3d1357
6fb0534
6e5debc
4259c7c
4cd00d5
b4781b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// 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.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: ProjectInfoManager | ||
) = | ||
|
||
static member GetFormattingChanges(document: Document, sourceText: SourceText, checker: FSharpChecker, optionsOpt: FSharpProjectOptions option, position: int) = | ||
// Logic should be: | ||
// If first token on the current line is a closing brace, | ||
// match the indent with the indent on the line that opened it | ||
|
||
asyncMaybe { | ||
let! options = optionsOpt | ||
|
||
let line = sourceText.Lines.[sourceText.Lines.IndexOf position] | ||
|
||
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.FilePath, options.OtherOptions |> List.ofArray) | ||
|
||
let tokens = Tokenizer.tokenizeLine(document.Id, sourceText, line.Start, document.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, document.FilePath, options, 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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, or perhaps check this before hand if a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tested with startIndent 0 and everything's fine :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
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 optionsOpt = projectInfoManager.TryGetOptionsForEditingDocumentOrProject document | ||
let! textChange = FSharpEditorFormattingService.GetFormattingChanges(document, sourceText, checkerProvider.Checker, optionsOpt, position) | ||
|
||
return | ||
match textChange with | ||
| Some change -> | ||
List([change]) :> IList<_> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We normally use the name |
||
| None -> | ||
List() :> IList<_> | ||
} | ||
|
||
interface IEditorFormattingService with | ||
member val SupportsFormatDocument = false | ||
member val SupportsFormatSelection = false | ||
member val SupportsFormatOnPaste = false | ||
member val SupportsFormatOnReturn = true | ||
|
||
override __.SupportsFormattingOnTypedCharacter (_document, ch) = | ||
match ch with | ||
| ')' | ']' | '}' -> true | ||
| _ -> false | ||
|
||
override __.GetFormattingChangesAsync (_document, _span, _cancellationToken) = null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Roslyn checks whether SupportsFormatSelection is true or SupportsFormatDocument is true before calling this. You're right though - I can just make it return an empty enumerable. |
||
|
||
override __.GetFormattingChangesOnPasteAsync (_document, _span, _cancellationToken) = null | ||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is realistic or would ever get hit, but
max 0 (sourceText.Lines.IndexOf position)
would be useful.Perhaps this is just me being paranoid after #3180.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IndexOf throws if the position isn't within the text, but as Roslyn has already supplied us with the position I think it's impossible that we can't find the line in the source text (also supplied by Roslyn).