Provide a better experience editing a file with merge-markers in it.#15850
Provide a better experience editing a file with merge-markers in it.#15850CyrusNajmabadi merged 10 commits intodotnet:masterfrom
Conversation
|
Tagging @dotnet/roslyn-compiler @dotnet/roslyn-ide . This is a new feature, and i don't think meets the bar for VS2017. However, this is a super nice thing to add and it makes the experience around editing merge-conflict files so much nicer than today. I would like to get this into post-dev15. Note: tests are forthcoming. I just wanted to get the initial code in so people could take a look. This code is really a straight port of the exact same feature we did in TypeScript several years back. |
|
I also added this feature to make working with merge conflicts nicer: #15853 |
|
I have done the VB side of this as well. So i would appreciate VB and C# eyes on this. |
f775766 to
dafedbd
Compare
| Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax | ||
| Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax | ||
| Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax | ||
| Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax |
|
|
||
| // All conflict markers consist of the same character repeated seven times. If it is | ||
| // a <<<<<<< or >>>>>>> marker then it is also followed by a space. | ||
| private static readonly int s_conflictMarkerLength = "<<<<<<<".Length; |
There was a problem hiding this comment.
... = 7; seems simpler, especially since you have a comment already ;-)
There was a problem hiding this comment.
I far prefer the "string".Length pattern. The code is then self-describing and good, regardless of the state of the comment.
| var text = TextWindow.Text; | ||
| if (position == 0 || SyntaxFacts.IsNewLine(text[position - 1])) | ||
| { | ||
| var firstCh = text[position]; |
There was a problem hiding this comment.
firstCh [](start = 20, length = 7)
Maybe check or assert that firstCh is one of '<', '>' or '='?
There was a problem hiding this comment.
i can do that.
| var startCh = this.TextWindow.PeekChar(); | ||
|
|
||
| // First create a trivia from the start of this merge conflict marker to the | ||
| // end of line/file (whicever comes first). |
There was a problem hiding this comment.
whicever [](start = 33, length = 8)
s/whicever/whichever/
|
Anything else @jcouv |
|
We need to chat before merging this. It's essentially adding a feature to the compiler layer. That means it's putting us on the hook for future bug fixes, etc ... |
|
Sure. We can talk about this. I can def see how this is a feature (since there are API changes for this). However, the core idea here is that this is just better error recovery. Right now our error recovery is basically horrendous/non-existent. Leading to a fairly terrible user experience in a reasonably common editing case. As this is really just about error-recovery, we always reserve the right to change how we behave here (including removing all this code entirely if we want). We've always asserted that error files may change in terms of how our APIs present them (including at the syntactic level). Also, from doing this in TS, the long term cost is effectively nothing. AFAIK there hasn't ever even been a single bug that needed to get fixed here. So, while it is a feature, it's an extremely low cost one. I'm hoping the bang/buck equation makes this an easy choice. -- Also, from a selfish Roslyn perspective, i think this is quite helpful to the team. Working with unmerged files today is basically extremely unpleasant in the IDE. You really have no choice but to treat the file as a blob of text that you manipulate roughly, while giving up most features. This tiny feature suddenly makes it completely reasonable and effective to work with these files jsut as you would any other C#/VB file. This turned out to be quite nice for TS development when this was added. |
| break; | ||
| } | ||
|
|
||
| if (ch == '>' && IsConflictMarkerTrivia()) |
There was a problem hiding this comment.
Do you want to accept conflict markers that are anywhere but the beginning of a line? #Resolved
There was a problem hiding this comment.
Ah, I see that is tested by IsConflictMarkerTrivia. #Resolved
There was a problem hiding this comment.
IsConflictMarkerTrivia actually verifies that it's at the start of a line. Which is pretty much all that we want since htat's what you always get from Git.
|
Also worth noting: this is effectively a straight port of the TS code I wrote for this. |
| ERR_PublicSignNetModule = 37282 | ||
| ERR_BadAssemblyName = 37283 | ||
|
|
||
| ERR_Merge_conflict_marker_encountered = 37282 |
There was a problem hiding this comment.
clearly this needs to be updated.
jaredpar
left a comment
There was a problem hiding this comment.
We've discussed with @Pilchie and @CyrusNajmabadi. This is a feature the IDE will own within the compiler.
|
@jaredpar Any other concerns you have? Or is this ready for review? Tnx! |
|
@CyrusNajmabadi i'm always concerned 😄 Fine for review. |
|
Great :) Glad to work toward making you bald like me. @dotnet/roslyn-compiler Please review when convenient (though ideally sooner rather than later) :) |
|
@dotnet/roslyn-ide Please review IDE sections as well. Thanks! |
|
|
||
| // All conflict markers consist of the same character repeated seven times. If it is | ||
| // a <<<<<<< or >>>>>>> marker then it is also followed by a space. | ||
| private static readonly int s_conflictMarkerLength = "<<<<<<<".Length; |
There was a problem hiding this comment.
Minor: I'd personally make the "<<<<<<<" a named constant. eg s_conflictMarker`
There was a problem hiding this comment.
I'm not certain what that buys. It's a value only used in a single place. Why introduce an indirection when it is already self-describing?
There was a problem hiding this comment.
Thought it was used else where, if it was it would make sense to share the content. So it had the same content.
|
@gafter can you take a look? @dotnet/roslyn-ide please review IDE parts. |
| return; | ||
| } | ||
|
|
||
| case '>': |
There was a problem hiding this comment.
case [](start = 20, length = 4)
A valid program can have >>>>>>> at the beginning of a line. e.g.
List<List<List<List<List<List<List<int
>>>>>>>
x = null;Are we intentionally breaking this "scenario"?
There was a problem hiding this comment.
Yes. We would be breaking that scenario. Up to you guys if you think such a break could realistically happen. Note: the issue occurs for TS, and we never got a single report about that.
There was a problem hiding this comment.
How about this idea: we stop recognizing >>>>>>> as a conflict marker, but continue having >>>>>>> end a skipped text section that is started with a =======. I think that would remove the compat break and still retain all the value of the feature. Does that work?
In reply to: 103549556 [](ancestors = 103549556)
There was a problem hiding this comment.
To clarify, the idea is that the skipped text section would be recognized by the scanner.
In reply to: 103551073 [](ancestors = 103551073,103549556)
There was a problem hiding this comment.
Ok. I think that is doable. Trying this out now.
|
@gafter Checking to see how feasible that would be. |
|
@gafter I have made that change (and added a test for the large-generic parsing case as well). |
|
@CyrusNajmabadi What happens it the "markers" are not valid markers but actually text. |
|
@AdamSpeight2008 Nothing happens. This is about lexing trivia. The contents of a string are not trivia and are consumed by the normal string lexing routine. |
| void M() | ||
| { | ||
| var v = new List<List<List<List<List<List<List<int | ||
| >>>>>>> |
There was a problem hiding this comment.
Please add a space after the last >.
|
@CyrusNajmabadi Thank-You for clarifying, I'm still learning. |
|
@AdamSpeight2008 We all are :) Glad to help. |
|
@gafter Will make that change and merge in. |
|
@dotnet/roslyn-ide Still need reviewers for the IDE sections. |
dpoeschl
left a comment
There was a problem hiding this comment.
IDE portions look good.
| } | ||
|
|
||
| [Fact, Trait(Traits.Feature, Traits.Features.Classification)] | ||
| public async Task TestConflictMarkers1() |
There was a problem hiding this comment.
Do we want a test here for the really, really generic case?
There was a problem hiding this comment.
Sure. I can add that. It's more a test that nothing goes horribly wrong (like crashing/index-out-of-bounds) vs actually verifying that we classify correctly.
|
|
||
| Private Sub ClassifyDisabledText(trivia As SyntaxTrivia, triviaList As SyntaxTriviaList) | ||
| Dim index = triviaList.IndexOf(trivia) | ||
| If index >= 2 AndAlso |
There was a problem hiding this comment.
Is there a way to share this code?
There was a problem hiding this comment.
Possibly now. It used to be not possible due to the different syntax models. Now that we've unified, tihs area is ripe for an overhaul. I'll add to my cleanup backlog.
| ERR_ExpressionTreeContainsDiscard = 8207, | ||
| #endregion more stragglers for C# 7 | ||
|
|
||
| ERR_Merge_conflict_marker_encountered = 8300, |
There was a problem hiding this comment.
Please use mixed case: ERR_MergeConflictMarkerEncountered

Fixes: #5028
This is a port of an extremely popular feature we did in TypeScript.
Today, if you try to edit a file with git merge-markers in it, you get a pretty terrible experience. Because of the text like
<<<<<<in the file, the parser gets very thrown off, and that impacts all subsequent features.To alleviate that, we augmented the TypeScript lexer to understand and be resilient to these merge markers, effectively making them part of our syntax model. Here's what things look like with the same feature applied to C#:
Note the clear errors indicating that we detected merge-conflicts.
In this system the higher levels then treat the merge-conflicts effectively as comments. So we classify them as comments, and we better support the code between them. In fact, in the 'top' part of the merge, you get a full C# experience (i.e. completion, highlight references, formatting, etc.). In the bottom part, we lexically-classify, but that's about it. In effect, it's treated similarly to an 'excluded code' region, except that we lexically classify since we're much more certain that is contains code.
--
A future feature i would lke to add would be a lightbulb/code-action that then easily let's you say "pick mine/theirs" to resolve merge conflicts :)