Repository navigation
Add support for custom outlining regions - #17954
Conversation
|
Ben Lichtman (@uniqueiniquity), |
| return regionText; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic in this whole 'if' block seems a little cumbersome. You're already using a RegExp to determine if it is a region. You should be able to use a capture group in the RegExp to extract the name also.
There was a problem hiding this comment.
Changed to use capture group.
There was a problem hiding this comment.
That's not the most efficient way to use capture groups. Simply do:
// Change above regexp to the below
// Not removal of the gm flags and moving the left paren of the capture group past the whitespace
const regionStart = new RegExp("^\\s*//\\s*#region\\s+(.*)?$");
// In here just do
const result = comment.match(regionStart);
return result[1] ? result[1] : regionText;There was a problem hiding this comment.
Now I've actually pushed the change. :)
| ////*/ | ||
| //// | ||
| /////* | ||
| ////// #endregion |
There was a problem hiding this comment.
It would be good to have tests with unbalanced start/end markers too. This is an area where a regression could easily slip through and cause exceptions.
There was a problem hiding this comment.
Added unbalanced tests.
| //////#endregion|] | ||
| //// | ||
| ////// Nested regions | ||
| ////[|// #region outer |
There was a problem hiding this comment.
Is there any way to test the name that appears for the region? (And add some tests for interesting region names, e.g. includes spaces or tabs).
There was a problem hiding this comment.
As far as I can tell, fourslash doesn't currently gather that information.
I can try to add the functionality if you would like me to though.
Bill Ticehurst (billti)
left a comment
There was a problem hiding this comment.
Some minor changes, else looks good. Thanks!
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; | ||
| const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); | ||
| const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)", "gm"); |
There was a problem hiding this comment.
\s [](start = 60, length = 3)
Exactly one? #Closed
There was a problem hiding this comment.
Yes; if the line ends after #endregion, it's fine, and if there's whitespace and then anything else, it's still fine, since we just want to know that the key phrase is actually there. #Closed
There was a problem hiding this comment.
Ah, I missed that this was a substring match (i.e. that the end of the line was open-ended).
| let depth = 0; | ||
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; | ||
| const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); |
There was a problem hiding this comment.
gm [](start = 72, length = 2)
What does this do?
There was a problem hiding this comment.
Removed the flags :) they weren't necessary anymore.
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; | ||
| const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); | ||
| const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)", "gm"); |
There was a problem hiding this comment.
endregion [](start = 50, length = 9)
Is there any way to associate the region's identifier with the #endregion? #Closed
There was a problem hiding this comment.
Yes, you can put anything you want there.
| let depth = 0; | ||
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; | ||
| const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); |
There was a problem hiding this comment.
region [](start = 52, length = 6)
Case sensitive? #Closed
There was a problem hiding this comment.
C# is case sensitive, so we're matching them. #Closed
| function addOutliningSpanRegions(regionSpan: RegionRange) { | ||
| if (regionSpan) { | ||
| const span: OutliningSpan = { | ||
| textSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), |
There was a problem hiding this comment.
createTextSpanFromBounds(regionSpan.pos, regionSpan.end) [](start = 30, length = 56)
Extract common sub-expression?
There was a problem hiding this comment.
Extracted.
| return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; | ||
| } | ||
|
|
||
| function isRegionStart(start: number, end: number) { |
There was a problem hiding this comment.
isRegionStart [](start = 17, length = 13)
It would appear to be a precondition that start is within a comment. Consider adding a comment to that effect.
There was a problem hiding this comment.
See new approach.
| const result = comment.match(regionStart); | ||
|
|
||
| if (result && result.length > 0) { | ||
| const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); |
There was a problem hiding this comment.
" " [](start = 53, length = 3)
Why space?
There was a problem hiding this comment.
Seems like you want to split on any whitespace.
There was a problem hiding this comment.
See new capture group approach.
| const result = comment.match(regionStart); | ||
|
|
||
| if (result && result.length > 0) { | ||
| const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); |
There was a problem hiding this comment.
function [](start = 65, length = 8)
Arrow expressions are nice in cases like this.
| const lineStarts = sourceFile.getLineStarts(); | ||
|
|
||
| for (const currentLineStart of lineStarts) { | ||
| const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); |
There was a problem hiding this comment.
getLineEndOfPosition [](start = 43, length = 20)
Couldn't you use the start position of the next line?
There was a problem hiding this comment.
Using start position of next line now.
| return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; | ||
| } | ||
|
|
||
| function isRegionStart(start: number, end: number) { |
There was a problem hiding this comment.
isRegionStart [](start = 17, length = 13)
This is a confusing name for a method that returns the name of the region.
There was a problem hiding this comment.
Changed.
Andrew Casey (amcasey)
left a comment
There was a problem hiding this comment.
LGTM, with minor comments
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; | ||
| const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); | ||
| const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)", "gm"); |
There was a problem hiding this comment.
Seeing as you now search line-by-line, as opposed to over the whole file, there is no value in the 'g' and 'm' flags on the regular expression. You are only looking for one match on one line at a time.
There was a problem hiding this comment.
Removed.
| //// | ||
| ////// #endregion outer|] | ||
| //// | ||
| ////// region delimiters not valid when preceding text on line |
There was a problem hiding this comment.
"when there is"?
There was a problem hiding this comment.
Added.
| @@ -0,0 +1,46 @@ | |||
| /// <reference path="fourslash.ts"/> | |||
|
|
|||
| ////// basic region | |||
There was a problem hiding this comment.
un-named/anonymous region?
There was a problem hiding this comment.
Renamed "region without label"
| function addOutliningSpanRegions(regionSpan: RegionRange) { | ||
| if (regionSpan) { | ||
| const span: OutliningSpan = { | ||
| textSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), |
There was a problem hiding this comment.
Use createTextSpanFromRange?
There was a problem hiding this comment.
Changed.
| } | ||
|
|
||
| function isRegionStart(start: number, end: number) { | ||
| if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { |
There was a problem hiding this comment.
There is also an isInComment helper which coerces the result into a boolean, but then you would effectively be doing a triple-negation. I'm still inclined to switching to the helper, but this is mostly a matter of taste.
There was a problem hiding this comment.
Used isInComment.
| const elements: OutliningSpan[] = []; | ||
| let depth = 0; | ||
| const regions: RegionRange[] = []; | ||
| const regionText = "#region"; |
There was a problem hiding this comment.
Maybe rename to regionDelimiter or regionStartDelimter? It's a bit confusing when reading isRegionStart below.
There was a problem hiding this comment.
Changed to "defaultLabel"
| return ""; | ||
| } | ||
|
|
||
| function isRegionEnd(start: number, end: number) { |
There was a problem hiding this comment.
does this return a boolean? Please rename if not, or coerce the result to a boolean via !!
There was a problem hiding this comment.
Coerced.
| end: -1; | ||
| } | ||
|
|
||
| export interface RegionRange extends TextRange { |
There was a problem hiding this comment.
should this be used in the definition of OutliningSpan now?
There was a problem hiding this comment.
also please move this to services/types.ts instead.
There was a problem hiding this comment.
i see now this is only used internally while building the spans. then this should move to src/services/outliningElementsCollector.ts and not be exported.
There was a problem hiding this comment.
Moved.
| const currentLineStart = lineStarts[i]; | ||
| const lineEnd = lineStarts[i + 1] - 1 || sourceFile.getEnd(); | ||
|
|
||
| const name = getRegionName(currentLineStart, lineEnd); |
There was a problem hiding this comment.
looks like you are calling isInComment a few times every line. I would say this is more expensive than the other checks, which is basically does the line start with // or not.
consider making this check first. then after that run your regexp, and if that all passes, then call isInComment before adding the region.
There was a problem hiding this comment.
Also consider combining your regexps into one. this way you run the regexp and gets you name + end/start marker.
In other words, combine isRegionEnd and getRegionName and inline them in this loop.
There was a problem hiding this comment.
Combined the regexp and moved the isInComment after we know it's a relevant line.
| } | ||
|
|
||
| function addOutliningSpanRegions(regionSpan: RegionRange) { | ||
| if (regionSpan) { |
There was a problem hiding this comment.
why not just create OutliningSpans for every region when we are building them instead of creating the extra object. less garbage to collect later.
There was a problem hiding this comment.
Fixed.
9c25611 to
3dfeb2d
Compare
| const collapseText = "..."; | ||
| const maxDepth = 20; | ||
| const defaultLabel = "#region"; | ||
| const regionMatch = new RegExp("^\\s*//\\s*(#region|#endregion)(?:\\s+(.*))?$"); |
There was a problem hiding this comment.
consider making end optional, this way you can just check on the truthiness of result[1]
new RegExp("^\\s*//\\s*#(end)?region(?:\\s+(.*))?$");
| else { | ||
| const region = regions.pop(); | ||
| if (region) { | ||
| const newTextSpan = createTextSpanFromBounds(region.textSpan.start, lineEnd); |
There was a problem hiding this comment.
why not just region.textSpan.length = linEnd -region.textSpan.start; and avoid creating the new object.
|
Is this a fix for VS Code, VS 2015 and/or VS2017? |
|
This is a fix for VS2017 and other editors that gather collapsing regions using the TS language service. |
Fixes #11073.
This update enables support for custom outlining regions of the following form
Region delimiters may not be preceded by any non-whitespace text, and they are not valid if they occur within a multiline comment.