Skip to content
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

Increase indent level after line end with certain keywords #3196

Merged
merged 11 commits into from
Jun 20, 2017

Conversation

xuanduc987
Copy link
Contributor

No unit test because I don't know how to assert these indent level :(

@msftclas
Copy link

msftclas commented Jun 9, 2017

@xuanduc987,
Thanks for having already signed the Contribution License Agreement. Your agreement was validated by Microsoft. We will now review your pull request.
Thanks,
Microsoft Pull Request Bot

@dsyme
Copy link
Contributor

dsyme commented Jun 9, 2017

Some test failurs in part1:

1) Failed : Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.IndentationServiceTests.TestIndentation(Some(0),6,"\r\n// Learn more about F# at http://fsharp.org\r\n// See the 'F# Tutorial' project for more help.\r\n\r\n[<EntryPoint>]\r\nlet main argv = \r\n    printfn \"%A\" argv\r\n    0 // return an integer exit code")
  Indentation on line 6 doesn't match
  Expected: 0
  But was:  4
at Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.IndentationServiceTests.TestIndentation(FSharpOption`1 expectedIndentation, Int32 lineNumber, String template) in D:\j\workspace\release_ci_pa---3f142ccc\vsintegration\tests\unittests\IndentationServiceTests.fs:line 69

2) Failed : Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.IndentationServiceTests.TestIndentation(Some(0),4,"\r\nnamespace ProjectNamespace\r\n\r\ntype Class1() = \r\n    member this.X = \"F#\"")
  Indentation on line 4 doesn't match
  Expected: 0
  But was:  4
at Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn.IndentationServiceTests.TestIndentation(FSharpOption`1 expectedIndentation, Int32 lineNumber, String template) in D:\j\workspace\release_ci_pa---3f142ccc\vsintegration\tests\unittests\IndentationServiceTests.fs:line 69

@dsyme
Copy link
Contributor

dsyme commented Jun 9, 2017

Adjusting the tests should be adequate test coverage

@xuanduc987
Copy link
Contributor Author

🤦‍♂️
I was looking for test with trait LanguageService.

@saul
Copy link
Contributor

saul commented Jun 9, 2017

Shouldn't we be using the tokenizer here instead of regexes to match keywords?

)
\s*$"
if Regex.IsMatch(previousLine.ToString(), regex, RegexOptions.IgnorePatternWhitespace) then
Some ((lastIndent/tabSize + 1) * tabSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just Some (lastIndent + tabSize)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logically, yes, technically no, if the indent is not a multiple of tabSize.

> let lastIndent = 3;;
val lastIndent : int = 3

> let tabSize = 4;;
val tabSize : int = 4

> (lastIndent/tabSize + 1) * tabSize;;
val it : int = 4

> lastIndent + tabSize;;
val it : int = 7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's consistent with L42 above

Copy link
Member

@KevinRansom KevinRansom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

| begin | do | class | function | then | else | struct | try
)
\s*$"
if Regex.IsMatch(previousLine.ToString(), regex, RegexOptions.IgnorePatternWhitespace) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuanduc987

Hi,
@brettfo just pointed out that lines ending with comments may give an issue, here.
both for single line comments // and comment blocks (*

What do you propose we in those circumstances?

Kevin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KevinRansom
A better approach should be like @saul said, using tokenizer but I honestly don't know how.
I think comments end with the above keywords are not as common as normal code though.

@saul
Copy link
Contributor

saul commented Jun 13, 2017

@xuanduc987 use Tokenizer.tokenizeLine. See the ImplementInterfaceCodeFixProvider.fs file for example usage: https://github.com/Microsoft/visualfsharp/blob/6f5b2c4d78ab386a1b03a65457bdc7f8561635d0/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs#L150

Hopefully that's enough to get you started :)

@xuanduc987
Copy link
Contributor Author

@saul
Thank you, I had looked at it earlier but was discouraged to see it depends on ProjectInfoManager, and it's async and what not.
I will take a stab at it again later :)

@saul
Copy link
Contributor

saul commented Jun 13, 2017

@xuanduc987 there's no reason why you can't change us from ISynchronousIndentationService to IIndentationService. They have the same API (apart from the latter returning a Task)

@xuanduc987
Copy link
Contributor Author

@saul Thanks for the pointer, I had push some commits to use Tokenizer.
It would be a huge help if you can review it :)

Copy link
Contributor

@saul saul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good :) I have a couple of questions though:

Does this also indent after the following code?

let foo () = <enter>

Or:

{ new MyInterface with <enter>

Also please can write some unit tests for the cases that you've added? This looks fantastic so far

match lastToken with
| NeedIndent -> (lastIndent/tabSize + 1) * tabSize
| _ -> lastIndent
}

interface ISynchronousIndentationService with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use IIndentationService instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the different but seems like C# and VB also use ISynchronousIndentationService

loop xs
else Some x

return! loop (List.rev tokens)
Copy link
Contributor

@saul saul Jun 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get rid of the loop function and this can just become tokens |> List.rev |> List.tryFind (fun x -> x.Tag <> FSharpTokenTag.WHITESPACE)


maybe {
// No indentation on the first line of a document
if lineNumber = 0 then return! None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need the else branch of this if statement - tryFindPreviousNonEmptyLine already checks if lineNumber is 0.

| _ -> spaces
Some (loop 0 0)

let rec tryFindLastNoneEmptyToken (line: TextLine) = maybe {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename this from tryFindLastNoneEmptyToken to tryFindLastNonWhitespaceToken

@saul
Copy link
Contributor

saul commented Jun 14, 2017

@KevinRansom if @xuanduc987 is happy with this I think this is good to merge 👍

@xuanduc987
Copy link
Contributor Author

@dotnet-bot test Windows_NT Release_ci_part3 Build please

@xuanduc987
Copy link
Contributor Author

@saul @KevinRansom All green 💯

try
failwith \"fail\"
with
| :? System.Exception -> \"error\"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't testing whether this line gets indented.

Also these test cases would be far easier to read if instead of specifying which lines we expect indentation in a separate array, we specified them inline in the template.

For example, these few lines would become:

try
    failwith \"fail\" // $Indent: 4$
with
    | :? System.Exception -> \"error\" // $Indent: 4$

You could then parse the template like:

let indentComment = System.Text.RegularExpressions.Regex(@"\/\/\s*\$\s*Indent:\s*(\d+)\s*\$")

template.Split [|'\n'|]
|> Seq.map (fun s -> s.Trim())
|> Seq.indexed
|> Seq.choose (fun (line, text) ->
    let m = indentComment.Match text
    if m.Success then Some (line, System.Convert.ToInt32 m.Groups.[1].Value)
    else None
)

This will return you a list of lineNumber * indentSize.

Copy link
Contributor Author

@xuanduc987 xuanduc987 Jun 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.
As for the line after with, it would not be auto indented, because match x with <enter> should not.

@saul
Copy link
Contributor

saul commented Jun 14, 2017

Fantastic 😃

@KevinRansom KevinRansom merged commit 4ed4eb0 into dotnet:master Jun 20, 2017
nosami pushed a commit to xamarin/visualfsharp that referenced this pull request Jan 26, 2022
* Increase indent level after line end with certain keywords

* Fix test case

* Use Tokenizer

* Refactoring and fix test

* Saul review

* Add more test

* Fix tests

* Rename tryFindLastNoneEmptyToken -> tryFindLastNoneWhitespaceToken

* Trailing comment is ok

* Move auto indent to a separate test

* Use same indentation as the previous comment line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants