From 9726ba1198dddce9dc2d8a5508d1dc8904185444 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 8 Aug 2017 09:50:07 -0700 Subject: [PATCH 01/14] Add support for custom outlining regions --- src/compiler/types.ts | 4 ++ src/services/outliningElementsCollector.ts | 69 ++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 608bc779042df..4bf180c595a52 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2037,6 +2037,10 @@ namespace ts { end: -1; } + export interface RegionRange extends TextRange { + name?: string; + } + // represents a top level: { type } expression in a JSDoc comment. export interface JSDocTypeExpression extends TypeNode { kind: SyntaxKind.JSDocTypeExpression; diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index e0d99d1d27165..3bd53f8630136 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -6,6 +6,10 @@ namespace ts.OutliningElementsCollector { export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; let depth = 0; + const regions: RegionRange[] = []; + const regionText = "#region"; + const regionStart = new RegExp("// #region( .+| *)", "g"); + const regionEnd = new RegExp("// #endregion *"); walk(sourceFile); return elements; @@ -35,6 +39,18 @@ namespace ts.OutliningElementsCollector { } } + function addOutliningSpanRegions(regionSpan: RegionRange) { + if (regionSpan) { + const span: OutliningSpan = { + textSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), + hintSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), + bannerText: regionSpan.name, + autoCollapse: false, + }; + elements.push(span); + } + } + function addOutliningForLeadingCommentsForNode(n: Node) { const comments = ts.getLeadingCommentRangesOfNode(n, sourceFile); @@ -89,12 +105,65 @@ namespace ts.OutliningElementsCollector { return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; } + function isRegionStart(range: CommentRange) { + const comment = sourceFile.text.substring(range.pos, range.end); + const result = comment.match(regionStart); + + if (result && result.length > 0) { + const name = result[0].substring(10).trim(); + if (name) { + return name; + } + else { + return regionText; + } + } + return ""; + } + + function isRegionEnd(range: CommentRange) { + const comment = sourceFile.text.substring(range.pos, range.end); + return comment.match(regionEnd); + } + + function addRegionsNearNode(n: Node) { + const comments = ts.getLeadingCommentRangesOfNode(n, sourceFile); + + if (n.kind !== SyntaxKind.SourceFile && comments) { + for (const currentComment of comments) { + cancellationToken.throwIfCancellationRequested(); + + if (currentComment.kind === SyntaxKind.SingleLineCommentTrivia) { + const name = isRegionStart(currentComment); + if (name) { + const region: RegionRange = { + pos: currentComment.pos, + end: currentComment.end, + name, + }; + regions.push(region); + } + else if (isRegionEnd(currentComment)) { + const region = regions.pop(); + + if (region) { + region.end = currentComment.end; + addOutliningSpanRegions(region); + } + } + } + } + } + } + function walk(n: Node): void { cancellationToken.throwIfCancellationRequested(); if (depth > maxDepth) { return; } + addRegionsNearNode(n); + if (isDeclaration(n)) { addOutliningForLeadingCommentsForNode(n); } From f91c23b25f63913124a60366fe7365424611144d Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 8 Aug 2017 15:58:42 -0700 Subject: [PATCH 02/14] Add regex sweep implementation --- src/services/outliningElementsCollector.ts | 55 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 3bd53f8630136..e79175b4b24c5 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -12,6 +12,7 @@ namespace ts.OutliningElementsCollector { const regionEnd = new RegExp("// #endregion *"); walk(sourceFile); + gatherRegions(); return elements; /** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */ @@ -127,7 +128,10 @@ namespace ts.OutliningElementsCollector { } function addRegionsNearNode(n: Node) { - const comments = ts.getLeadingCommentRangesOfNode(n, sourceFile); + const precedingToken = ts.findPrecedingToken(n.pos, sourceFile); + const trailingComments = precedingToken && ts.getTrailingCommentRanges(sourceFile.text, precedingToken.end); + const leadingComments = ts.getLeadingCommentRangesOfNode(n, sourceFile); + const comments = concatenate(trailingComments, leadingComments); if (n.kind !== SyntaxKind.SourceFile && comments) { for (const currentComment of comments) { @@ -148,7 +152,7 @@ namespace ts.OutliningElementsCollector { if (region) { region.end = currentComment.end; - addOutliningSpanRegions(region); + // addOutliningSpanRegions(region); } } } @@ -156,6 +160,53 @@ namespace ts.OutliningElementsCollector { } } + function isRegionStartBoundaries(start: number, end: number) { + const comment = sourceFile.text.substring(start, end); + const result = comment.match(regionStart); + + if (result && result.length > 0) { + const name = result[0].substring(10).trim(); + if (name) { + return name; + } + else { + return regionText; + } + } + return ""; + } + + function isRegionEndBoundaries(start: number, end: number) { + const comment = sourceFile.text.substring(start, end); + return comment.match(regionEnd); + } + + function gatherRegions(): void { + const lineStarts = sourceFile.getLineStarts(); + + for (const currentLineStart of lineStarts) { + const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); + + const name = isRegionStartBoundaries(currentLineStart, lineEnd); + if (name) { + const region: RegionRange = { + pos: currentLineStart, + end: lineEnd, + name, + }; + regions.push(region); + } + else if (isRegionEndBoundaries(currentLineStart, lineEnd)) { + const region = regions.pop(); + + if (region) { + region.end = lineEnd; + addOutliningSpanRegions(region); + } + } + } + } + function walk(n: Node): void { cancellationToken.throwIfCancellationRequested(); if (depth > maxDepth) { From 0b3ec247bcbce77115b4155688f5b718ce7f7ee1 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Wed, 9 Aug 2017 09:00:46 -0700 Subject: [PATCH 03/14] Fix name capture logic --- src/services/outliningElementsCollector.ts | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index e79175b4b24c5..255e7c9aad633 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -8,8 +8,8 @@ namespace ts.OutliningElementsCollector { let depth = 0; const regions: RegionRange[] = []; const regionText = "#region"; - const regionStart = new RegExp("// #region( .+| *)", "g"); - const regionEnd = new RegExp("// #endregion *"); + const regionStart = new RegExp("//\\s*#region(\\s+.*)?$", "gm"); + const regionEnd = new RegExp("//\\s*#endregion(\\s|$)", "gm"); walk(sourceFile); gatherRegions(); @@ -165,12 +165,23 @@ namespace ts.OutliningElementsCollector { const result = comment.match(regionStart); if (result && result.length > 0) { - const name = result[0].substring(10).trim(); - if (name) { - return name; + const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); + + if (sections[0] === "//") { + if (sections.length > 2) { + return result[0].substring(result[0].indexOf(sections[2])); + } + else { + return regionText; + } } else { - return regionText; + if (sections.length > 1) { + return result[0].substring(result[0].indexOf(sections[1])); + } + else { + return regionText; + } } } return ""; From 0ef5498de3aff69da3a7e56574f98e0b4e6b5e10 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Wed, 9 Aug 2017 09:13:18 -0700 Subject: [PATCH 04/14] Clean up unused functions --- src/services/outliningElementsCollector.ts | 64 ++-------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 255e7c9aad633..cad56fe1ced3c 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -106,61 +106,7 @@ namespace ts.OutliningElementsCollector { return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; } - function isRegionStart(range: CommentRange) { - const comment = sourceFile.text.substring(range.pos, range.end); - const result = comment.match(regionStart); - - if (result && result.length > 0) { - const name = result[0].substring(10).trim(); - if (name) { - return name; - } - else { - return regionText; - } - } - return ""; - } - - function isRegionEnd(range: CommentRange) { - const comment = sourceFile.text.substring(range.pos, range.end); - return comment.match(regionEnd); - } - - function addRegionsNearNode(n: Node) { - const precedingToken = ts.findPrecedingToken(n.pos, sourceFile); - const trailingComments = precedingToken && ts.getTrailingCommentRanges(sourceFile.text, precedingToken.end); - const leadingComments = ts.getLeadingCommentRangesOfNode(n, sourceFile); - const comments = concatenate(trailingComments, leadingComments); - - if (n.kind !== SyntaxKind.SourceFile && comments) { - for (const currentComment of comments) { - cancellationToken.throwIfCancellationRequested(); - - if (currentComment.kind === SyntaxKind.SingleLineCommentTrivia) { - const name = isRegionStart(currentComment); - if (name) { - const region: RegionRange = { - pos: currentComment.pos, - end: currentComment.end, - name, - }; - regions.push(region); - } - else if (isRegionEnd(currentComment)) { - const region = regions.pop(); - - if (region) { - region.end = currentComment.end; - // addOutliningSpanRegions(region); - } - } - } - } - } - } - - function isRegionStartBoundaries(start: number, end: number) { + function isRegionStart(start: number, end: number) { const comment = sourceFile.text.substring(start, end); const result = comment.match(regionStart); @@ -187,7 +133,7 @@ namespace ts.OutliningElementsCollector { return ""; } - function isRegionEndBoundaries(start: number, end: number) { + function isRegionEnd(start: number, end: number) { const comment = sourceFile.text.substring(start, end); return comment.match(regionEnd); } @@ -198,7 +144,7 @@ namespace ts.OutliningElementsCollector { for (const currentLineStart of lineStarts) { const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); - const name = isRegionStartBoundaries(currentLineStart, lineEnd); + const name = isRegionStart(currentLineStart, lineEnd); if (name) { const region: RegionRange = { pos: currentLineStart, @@ -207,7 +153,7 @@ namespace ts.OutliningElementsCollector { }; regions.push(region); } - else if (isRegionEndBoundaries(currentLineStart, lineEnd)) { + else if (isRegionEnd(currentLineStart, lineEnd)) { const region = regions.pop(); if (region) { @@ -224,8 +170,6 @@ namespace ts.OutliningElementsCollector { return; } - addRegionsNearNode(n); - if (isDeclaration(n)) { addOutliningForLeadingCommentsForNode(n); } From 4971e3152c2ba1d5256c986b936322aeb22be436 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Wed, 9 Aug 2017 10:14:52 -0700 Subject: [PATCH 05/14] Ensure region boundaries are entire line --- src/services/outliningElementsCollector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index cad56fe1ced3c..7c4bf24d5801f 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -8,8 +8,8 @@ namespace ts.OutliningElementsCollector { let depth = 0; const regions: RegionRange[] = []; const regionText = "#region"; - const regionStart = new RegExp("//\\s*#region(\\s+.*)?$", "gm"); - const regionEnd = new RegExp("//\\s*#endregion(\\s|$)", "gm"); + const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); + const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)", "gm"); walk(sourceFile); gatherRegions(); From 562d988614e967910f06da72ce9d0f57c7c8a39a Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Mon, 21 Aug 2017 11:19:21 -0700 Subject: [PATCH 06/14] Exclude region delimiters in multiline comments --- src/services/outliningElementsCollector.ts | 41 ++++++++++++---------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 7c4bf24d5801f..82ac22e644c76 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -107,26 +107,28 @@ namespace ts.OutliningElementsCollector { } function isRegionStart(start: number, end: number) { - const comment = sourceFile.text.substring(start, end); - const result = comment.match(regionStart); + if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { + const comment = sourceFile.text.substring(start, end); + const result = comment.match(regionStart); - if (result && result.length > 0) { - const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); + if (result && result.length > 0) { + const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); - if (sections[0] === "//") { - if (sections.length > 2) { - return result[0].substring(result[0].indexOf(sections[2])); - } - else { - return regionText; - } - } - else { - if (sections.length > 1) { - return result[0].substring(result[0].indexOf(sections[1])); + if (sections[0] === "//") { + if (sections.length > 2) { + return result[0].substring(result[0].indexOf(sections[2])); + } + else { + return regionText; + } } else { - return regionText; + if (sections.length > 1) { + return result[0].substring(result[0].indexOf(sections[1])); + } + else { + return regionText; + } } } } @@ -134,8 +136,11 @@ namespace ts.OutliningElementsCollector { } function isRegionEnd(start: number, end: number) { - const comment = sourceFile.text.substring(start, end); - return comment.match(regionEnd); + if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { + const comment = sourceFile.text.substring(start, end); + return comment.match(regionEnd); + } + return undefined; } function gatherRegions(): void { From fb462c91a4a4089a05eeb83a3d6b5333d4385c2d Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Mon, 21 Aug 2017 15:28:40 -0700 Subject: [PATCH 07/14] Ensure returned spans are ordered by start --- src/services/outliningElementsCollector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 82ac22e644c76..59f8e9f554921 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -13,7 +13,7 @@ namespace ts.OutliningElementsCollector { walk(sourceFile); gatherRegions(); - return elements; + return elements.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start); /** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */ function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean, useFullStart: boolean) { From 442bc56fc2b1a4ba862cd33895f3185dafc85044 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Mon, 21 Aug 2017 15:39:58 -0700 Subject: [PATCH 08/14] Add test for region spans --- .../fourslash/getOutliningSpansForRegions.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/cases/fourslash/getOutliningSpansForRegions.ts diff --git a/tests/cases/fourslash/getOutliningSpansForRegions.ts b/tests/cases/fourslash/getOutliningSpansForRegions.ts new file mode 100644 index 0000000000000..73202fb9c9c11 --- /dev/null +++ b/tests/cases/fourslash/getOutliningSpansForRegions.ts @@ -0,0 +1,46 @@ +/// + +////// basic region +////[|// #region +//// +////// #endregion|] +//// +////// region with label +////[|// #region label1 +//// +////// #endregion|] +//// +////// region with extra whitespace in all valid locations +////[| // #region label2 label3 +//// +//// // #endregion|] +//// +////// No space before directive +////[|//#region label4 +//// +//////#endregion|] +//// +////// Nested regions +////[|// #region outer +//// +////[|// #region inner +//// +////// #endregion inner|] +//// +////// #endregion outer|] +//// +////// region delimiters not valid when preceding text on line +//// test // #region invalid1 +//// +////test // #endregion +//// +////// region delimiters not valid when in multiline comment +/////* +////// #region invalid2 +////*/ +//// +/////* +////// #endregion +////*/ + +verify.outliningSpansInCurrentFile(test.ranges()); \ No newline at end of file From 509d347ab9c922daec779ef6c7c64b841d2e3201 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 22 Aug 2017 12:59:47 -0700 Subject: [PATCH 09/14] Region now starts at beginning of comment, and reviewer edits --- src/services/outliningElementsCollector.ts | 42 ++++++++----------- .../fourslash/getOutliningSpansForRegions.ts | 2 +- ...getOutliningSpansForUnbalancedEndRegion.ts | 10 +++++ .../getOutliningSpansForUnbalancedRegion.ts | 11 +++++ 4 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 tests/cases/fourslash/getOutliningSpansForUnbalancedEndRegion.ts create mode 100644 tests/cases/fourslash/getOutliningSpansForUnbalancedRegion.ts diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 59f8e9f554921..748085bd041ae 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -2,14 +2,14 @@ namespace ts.OutliningElementsCollector { const collapseText = "..."; const maxDepth = 20; + const regionText = "#region"; + const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$"); + const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)"); export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; let depth = 0; const regions: RegionRange[] = []; - const regionText = "#region"; - const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$", "gm"); - const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)", "gm"); walk(sourceFile); gatherRegions(); @@ -42,9 +42,10 @@ namespace ts.OutliningElementsCollector { function addOutliningSpanRegions(regionSpan: RegionRange) { if (regionSpan) { + const textSpan = createTextSpanFromBounds(regionSpan.pos, regionSpan.end); const span: OutliningSpan = { - textSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), - hintSpan: createTextSpanFromBounds(regionSpan.pos, regionSpan.end), + textSpan, + hintSpan: textSpan, bannerText: regionSpan.name, autoCollapse: false, }; @@ -106,29 +107,18 @@ namespace ts.OutliningElementsCollector { return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; } - function isRegionStart(start: number, end: number) { + function getRegionName(start: number, end: number) { if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { const comment = sourceFile.text.substring(start, end); const result = comment.match(regionStart); if (result && result.length > 0) { - const sections = result[0].split(" ").filter(function (s) { return s !== ""; }); - - if (sections[0] === "//") { - if (sections.length > 2) { - return result[0].substring(result[0].indexOf(sections[2])); - } - else { - return regionText; - } + const label = result.pop(); + if (label) { + return label.trim(); } else { - if (sections.length > 1) { - return result[0].substring(result[0].indexOf(sections[1])); - } - else { - return regionText; - } + return regionText; } } } @@ -146,13 +136,15 @@ namespace ts.OutliningElementsCollector { function gatherRegions(): void { const lineStarts = sourceFile.getLineStarts(); - for (const currentLineStart of lineStarts) { - const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); + for (let i = 0; i < lineStarts.length; i++) { + const currentLineStart = lineStarts[i]; + const lineEnd = lineStarts[i + 1] - 1 || sourceFile.getEnd(); - const name = isRegionStart(currentLineStart, lineEnd); + const name = getRegionName(currentLineStart, lineEnd); if (name) { + const start = sourceFile.getFullText().indexOf("//", currentLineStart); const region: RegionRange = { - pos: currentLineStart, + pos: start, end: lineEnd, name, }; diff --git a/tests/cases/fourslash/getOutliningSpansForRegions.ts b/tests/cases/fourslash/getOutliningSpansForRegions.ts index 73202fb9c9c11..80e1c3113cf33 100644 --- a/tests/cases/fourslash/getOutliningSpansForRegions.ts +++ b/tests/cases/fourslash/getOutliningSpansForRegions.ts @@ -11,7 +11,7 @@ ////// #endregion|] //// ////// region with extra whitespace in all valid locations -////[| // #region label2 label3 +//// [|// #region label2 label3 //// //// // #endregion|] //// diff --git a/tests/cases/fourslash/getOutliningSpansForUnbalancedEndRegion.ts b/tests/cases/fourslash/getOutliningSpansForUnbalancedEndRegion.ts new file mode 100644 index 0000000000000..c15e23eba7576 --- /dev/null +++ b/tests/cases/fourslash/getOutliningSpansForUnbalancedEndRegion.ts @@ -0,0 +1,10 @@ +/// + +////// bottom-heavy region balance +////[|// #region matched +//// +////// #endregion matched|] +//// +////// #endregion unmatched + +verify.outliningSpansInCurrentFile(test.ranges()); \ No newline at end of file diff --git a/tests/cases/fourslash/getOutliningSpansForUnbalancedRegion.ts b/tests/cases/fourslash/getOutliningSpansForUnbalancedRegion.ts new file mode 100644 index 0000000000000..f50aae713a52b --- /dev/null +++ b/tests/cases/fourslash/getOutliningSpansForUnbalancedRegion.ts @@ -0,0 +1,11 @@ +/// + +////// top-heavy region balance +////// #region unmatched +//// +////[|// #region matched +//// +////// #endregion matched|] + +debugger; +verify.outliningSpansInCurrentFile(test.ranges()); \ No newline at end of file From c3f2648ba4d851ed9ab2dec9ded1ced7fbf144a9 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 22 Aug 2017 13:59:02 -0700 Subject: [PATCH 10/14] Edits from aozgaa review and simplify regex --- src/services/outliningElementsCollector.ts | 22 +++++++++---------- .../fourslash/getOutliningSpansForRegions.ts | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 748085bd041ae..d4f70d52edaec 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -2,9 +2,9 @@ namespace ts.OutliningElementsCollector { const collapseText = "..."; const maxDepth = 20; - const regionText = "#region"; - const regionStart = new RegExp("^\\s*//\\s*#region(\\s+.*)?$"); - const regionEnd = new RegExp("^\\s*//\\s*#endregion(\\s|$)"); + const defaultLabel = "#region"; + const regionStart = new RegExp("^//\\s*#region(\\s+.*)?$"); + const regionEnd = new RegExp("^//\\s*#endregion(\\s|$)"); export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; @@ -42,7 +42,7 @@ namespace ts.OutliningElementsCollector { function addOutliningSpanRegions(regionSpan: RegionRange) { if (regionSpan) { - const textSpan = createTextSpanFromBounds(regionSpan.pos, regionSpan.end); + const textSpan = createTextSpanFromRange(regionSpan); const span: OutliningSpan = { textSpan, hintSpan: textSpan, @@ -108,8 +108,8 @@ namespace ts.OutliningElementsCollector { } function getRegionName(start: number, end: number) { - if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { - const comment = sourceFile.text.substring(start, end); + if (!isInComment(sourceFile, start)) { + const comment = sourceFile.text.substring(start, end).trim(); const result = comment.match(regionStart); if (result && result.length > 0) { @@ -118,7 +118,7 @@ namespace ts.OutliningElementsCollector { return label.trim(); } else { - return regionText; + return defaultLabel; } } } @@ -126,11 +126,11 @@ namespace ts.OutliningElementsCollector { } function isRegionEnd(start: number, end: number) { - if (!ts.formatting.getRangeOfEnclosingComment(sourceFile, start, /*onlyMultiLine*/ true)) { - const comment = sourceFile.text.substring(start, end); - return comment.match(regionEnd); + if (!isInComment(sourceFile, start)) { + const comment = sourceFile.text.substring(start, end).trim(); + return !!comment.match(regionEnd); } - return undefined; + return false; } function gatherRegions(): void { diff --git a/tests/cases/fourslash/getOutliningSpansForRegions.ts b/tests/cases/fourslash/getOutliningSpansForRegions.ts index 80e1c3113cf33..151526c6b9962 100644 --- a/tests/cases/fourslash/getOutliningSpansForRegions.ts +++ b/tests/cases/fourslash/getOutliningSpansForRegions.ts @@ -1,6 +1,6 @@ /// -////// basic region +////// region without label ////[|// #region //// ////// #endregion|] @@ -29,7 +29,7 @@ //// ////// #endregion outer|] //// -////// region delimiters not valid when preceding text on line +////// region delimiters not valid when there is preceding text on line //// test // #region invalid1 //// ////test // #endregion From 7781245f1e9994c992161699d5f832d5131d2753 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Fri, 15 Sep 2017 10:38:05 -0700 Subject: [PATCH 11/14] Move RegionRange to private scope --- src/compiler/types.ts | 4 ---- src/services/outliningElementsCollector.ts | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4bf180c595a52..608bc779042df 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2037,10 +2037,6 @@ namespace ts { end: -1; } - export interface RegionRange extends TextRange { - name?: string; - } - // represents a top level: { type } expression in a JSDoc comment. export interface JSDocTypeExpression extends TypeNode { kind: SyntaxKind.JSDocTypeExpression; diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index d4f70d52edaec..20456b5241bd2 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -6,6 +6,10 @@ namespace ts.OutliningElementsCollector { const regionStart = new RegExp("^//\\s*#region(\\s+.*)?$"); const regionEnd = new RegExp("^//\\s*#endregion(\\s|$)"); + interface RegionRange extends TextRange { + name?: string; + } + export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; let depth = 0; From 3dfeb2d0f4274169e2974abe52e4213d666304d3 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Fri, 15 Sep 2017 15:33:34 -0700 Subject: [PATCH 12/14] Combine and simplify regex --- src/services/outliningElementsCollector.ts | 65 ++++++------------- .../fourslash/getOutliningSpansForRegions.ts | 5 ++ 2 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 20456b5241bd2..e215663372bed 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -3,8 +3,7 @@ namespace ts.OutliningElementsCollector { const collapseText = "..."; const maxDepth = 20; const defaultLabel = "#region"; - const regionStart = new RegExp("^//\\s*#region(\\s+.*)?$"); - const regionEnd = new RegExp("^//\\s*#endregion(\\s|$)"); + const regionMatch = new RegExp("^\\s*//\\s*(#region|#endregion)(?:\\s+(.*))?$"); interface RegionRange extends TextRange { name?: string; @@ -111,55 +110,31 @@ namespace ts.OutliningElementsCollector { return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; } - function getRegionName(start: number, end: number) { - if (!isInComment(sourceFile, start)) { - const comment = sourceFile.text.substring(start, end).trim(); - const result = comment.match(regionStart); - - if (result && result.length > 0) { - const label = result.pop(); - if (label) { - return label.trim(); - } - else { - return defaultLabel; - } - } - } - return ""; - } - - function isRegionEnd(start: number, end: number) { - if (!isInComment(sourceFile, start)) { - const comment = sourceFile.text.substring(start, end).trim(); - return !!comment.match(regionEnd); - } - return false; - } - function gatherRegions(): void { const lineStarts = sourceFile.getLineStarts(); for (let i = 0; i < lineStarts.length; i++) { const currentLineStart = lineStarts[i]; const lineEnd = lineStarts[i + 1] - 1 || sourceFile.getEnd(); - - const name = getRegionName(currentLineStart, lineEnd); - if (name) { - const start = sourceFile.getFullText().indexOf("//", currentLineStart); - const region: RegionRange = { - pos: start, - end: lineEnd, - name, - }; - regions.push(region); - } - else if (isRegionEnd(currentLineStart, lineEnd)) { - const region = regions.pop(); - - if (region) { - region.end = lineEnd; - addOutliningSpanRegions(region); + const comment = sourceFile.text.substring(currentLineStart, lineEnd); + const result = comment.match(regionMatch); + + if (result && !isInComment(sourceFile, currentLineStart)) { + if (result[1] === "#region") { + const start = sourceFile.getFullText().indexOf("//", currentLineStart); + const region: RegionRange = { + pos: start, + end: lineEnd, + name: result[2] || defaultLabel, + }; + regions.push(region); + } + else { + const region = regions.pop(); + if (region) { + region.end = lineEnd; + addOutliningSpanRegions(region); + } } } } diff --git a/tests/cases/fourslash/getOutliningSpansForRegions.ts b/tests/cases/fourslash/getOutliningSpansForRegions.ts index 151526c6b9962..fcd71e29ef13f 100644 --- a/tests/cases/fourslash/getOutliningSpansForRegions.ts +++ b/tests/cases/fourslash/getOutliningSpansForRegions.ts @@ -5,6 +5,11 @@ //// ////// #endregion|] //// +////// region without label with trailing spaces +////[|// #region +//// +////// #endregion|] +//// ////// region with label ////[|// #region label1 //// From 484bd2082ee8f92d4d604ae610afe654ff9ab8b1 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Fri, 15 Sep 2017 16:15:32 -0700 Subject: [PATCH 13/14] Refactored out RegionRange --- src/services/outliningElementsCollector.ts | 35 +++++++--------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index e215663372bed..f5c9754cea844 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -5,14 +5,10 @@ namespace ts.OutliningElementsCollector { const defaultLabel = "#region"; const regionMatch = new RegExp("^\\s*//\\s*(#region|#endregion)(?:\\s+(.*))?$"); - interface RegionRange extends TextRange { - name?: string; - } - export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; let depth = 0; - const regions: RegionRange[] = []; + const regions: OutliningSpan[] = []; walk(sourceFile); gatherRegions(); @@ -43,19 +39,6 @@ namespace ts.OutliningElementsCollector { } } - function addOutliningSpanRegions(regionSpan: RegionRange) { - if (regionSpan) { - const textSpan = createTextSpanFromRange(regionSpan); - const span: OutliningSpan = { - textSpan, - hintSpan: textSpan, - bannerText: regionSpan.name, - autoCollapse: false, - }; - elements.push(span); - } - } - function addOutliningForLeadingCommentsForNode(n: Node) { const comments = ts.getLeadingCommentRangesOfNode(n, sourceFile); @@ -122,18 +105,22 @@ namespace ts.OutliningElementsCollector { if (result && !isInComment(sourceFile, currentLineStart)) { if (result[1] === "#region") { const start = sourceFile.getFullText().indexOf("//", currentLineStart); - const region: RegionRange = { - pos: start, - end: lineEnd, - name: result[2] || defaultLabel, + const textSpan = createTextSpanFromBounds(start, lineEnd); + const region: OutliningSpan = { + textSpan, + hintSpan: textSpan, + bannerText: result[2] || defaultLabel, + autoCollapse: false }; regions.push(region); } else { const region = regions.pop(); if (region) { - region.end = lineEnd; - addOutliningSpanRegions(region); + const newTextSpan = createTextSpanFromBounds(region.textSpan.start, lineEnd); + region.textSpan = newTextSpan; + region.hintSpan = newTextSpan; + elements.push(region); } } } From e5c43cddb74406cf3df59f672ce99ca866ffd83d Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Fri, 15 Sep 2017 16:47:59 -0700 Subject: [PATCH 14/14] Remove extra OutliningSpan and simplify regex --- src/services/outliningElementsCollector.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index f5c9754cea844..03ae0529ca9d9 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -3,7 +3,7 @@ namespace ts.OutliningElementsCollector { const collapseText = "..."; const maxDepth = 20; const defaultLabel = "#region"; - const regionMatch = new RegExp("^\\s*//\\s*(#region|#endregion)(?:\\s+(.*))?$"); + const regionMatch = new RegExp("^\\s*//\\s*#(end)?region(?:\\s+(.*))?$"); export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; @@ -103,7 +103,7 @@ namespace ts.OutliningElementsCollector { const result = comment.match(regionMatch); if (result && !isInComment(sourceFile, currentLineStart)) { - if (result[1] === "#region") { + if (!result[1]) { const start = sourceFile.getFullText().indexOf("//", currentLineStart); const textSpan = createTextSpanFromBounds(start, lineEnd); const region: OutliningSpan = { @@ -117,9 +117,8 @@ namespace ts.OutliningElementsCollector { else { const region = regions.pop(); if (region) { - const newTextSpan = createTextSpanFromBounds(region.textSpan.start, lineEnd); - region.textSpan = newTextSpan; - region.hintSpan = newTextSpan; + region.textSpan.length = lineEnd - region.textSpan.start; + region.hintSpan.length = lineEnd - region.textSpan.start; elements.push(region); } }