Skip to content

Commit dab27e8

Browse files
authored
Merge pull request #22 from 1nVitr0/develop
Merge pull request #21 from 1nVitr0:bugfix/multilevel-sorting-block-markers
2 parents d30f207 + 527c670 commit dab27e8

File tree

7 files changed

+131
-73
lines changed

7 files changed

+131
-73
lines changed

src/providers/BlockSortProvider.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Range, Selection, TextDocument } from "vscode";
2-
import ConfigurationProvider from "./ConfigurationProvider";
3-
import StringProcessingProvider, { Folding } from "./StringProcessingProvider";
1+
import { Range, Selection, TextDocument } from 'vscode';
2+
import ConfigurationProvider from './ConfigurationProvider';
3+
import StringProcessingProvider, { Folding } from './StringProcessingProvider';
44

5-
type SortingStrategy = "asc" | "desc" | "ascNatural" | "descNatural";
5+
type SortingStrategy = 'asc' | 'desc' | 'ascNatural' | 'descNatural';
66

77
export default class BlockSortProvider {
88
public static sort: Record<SortingStrategy, (a: string, b: string) => number> = {
@@ -15,12 +15,13 @@ export default class BlockSortProvider {
1515
protected static padNumbers(line: string) {
1616
const { omitUuids, padding, sortNegativeValues } = ConfigurationProvider.getNaturalSortOptions();
1717
let result = line;
18-
if (omitUuids) result = result.replace(/\d+(?=[^a-zA-z]|$)|(?<=[^a-zA-z]|^)\d+/g, (match) => match.padStart(padding, "0"));
19-
else result = result.replace(/\d+/g, (match) => match.padStart(padding, "0"));
18+
if (omitUuids)
19+
result = result.replace(/\d+(?=[^a-zA-z]|$)|(?<=[^a-zA-z]|^)\d+/g, (match) => match.padStart(padding, '0'));
20+
else result = result.replace(/\d+/g, (match) => match.padStart(padding, '0'));
2021

2122
if (sortNegativeValues) {
2223
result = result.replace(
23-
new RegExp(`-\\d{${padding}}`, "g"),
24+
new RegExp(`-\\d{${padding}}`, 'g'),
2425
(match) => `-${(Math.pow(10, padding) + parseInt(match)).toString()}`
2526
);
2627
}
@@ -46,20 +47,20 @@ export default class BlockSortProvider {
4647
textBlocks = textBlocks.map((block) => this.sortBlockHeaders(block, sort));
4748

4849
if (this.stringProcessor.isList(blocks) && textBlocks.length && !/,$/.test(textBlocks[textBlocks.length - 1])) {
49-
textBlocks[textBlocks.length - 1] += ",";
50+
textBlocks[textBlocks.length - 1] += ',';
5051
this.applySort(textBlocks, sort);
51-
textBlocks[textBlocks.length - 1] = textBlocks[textBlocks.length - 1].replace(/,\s*$/, "");
52+
textBlocks[textBlocks.length - 1] = textBlocks[textBlocks.length - 1].replace(/,\s*$/, '');
5253
} else {
5354
this.applySort(textBlocks, sort);
5455
}
5556

5657
if (textBlocks.length && !textBlocks[0].trim()) {
57-
textBlocks.push(textBlocks.shift() || "");
58+
textBlocks.push(textBlocks.shift() || '');
5859
} else if (textBlocks.length && /^\s*\r?\n/.test(textBlocks[0])) {
5960
// For some reason a newline for the second block gets left behind sometimes
6061
const front = !/\r?\n$/.test(textBlocks[0]) && textBlocks[1] && !/^\r?\n/.test(textBlocks[1]);
61-
textBlocks[0] = textBlocks[0].replace(/^\s*\r?\n/, "");
62-
textBlocks[front ? 0 : textBlocks.length - 1] += "\n";
62+
textBlocks[0] = textBlocks[0].replace(/^\s*\r?\n/, '');
63+
textBlocks[front ? 0 : textBlocks.length - 1] += '\n';
6364
}
6465

6566
return textBlocks;
@@ -69,7 +70,7 @@ export default class BlockSortProvider {
6970
const startLine = range.start.line;
7071
const text = this.document.getText(range);
7172
const lines = text.split(/\r?\n/);
72-
const firstLine = lines.shift() || "";
73+
const firstLine = lines.shift() || '';
7374
const initialIndent = this.stringProcessor.getIndent(firstLine);
7475
const blocks: Range[] = [];
7576

@@ -82,15 +83,15 @@ export default class BlockSortProvider {
8283
if (
8384
validBlock &&
8485
this.stringProcessor.stripComments(currentBlock).trim() &&
85-
!this.stringProcessor.isInCompleteBlock(currentBlock) &&
86+
!this.stringProcessor.isIncompleteBlock(currentBlock) &&
8687
(!this.stringProcessor.isIndentIgnoreLine(line) || this.stringProcessor.isCompleteBlock(currentBlock)) &&
8788
this.stringProcessor.getIndent(line) === initialIndent &&
8889
!this.stringProcessor.hasFolding(folding)
8990
) {
9091
blocks.push(this.document.validateRange(new Range(startLine + lastStart, 0, startLine + currentEnd, Infinity)));
9192
lastStart = currentEnd + 1;
9293
currentEnd = lastStart;
93-
currentBlock = "";
94+
currentBlock = '';
9495
validBlock = false;
9596
} else {
9697
currentEnd++;
@@ -185,14 +186,16 @@ export default class BlockSortProvider {
185186
if (sortChildren === 0) return this.document.getText(block);
186187

187188
let blocks = this.getInnerBlocks(block);
188-
if (!blocks.length) return this.document.getText(block);
189+
// if (!blocks.length || (blocks.length === 1 && blocks[0].isSingleLine)) return this.document.getText(block);
189190

190191
const head: Range = new Range(block.start, blocks[0]?.start || block.start);
191192
const tail: Range = new Range(blocks[blocks.length - 1]?.end || block.end, block.end);
192193

194+
if (head.isEmpty && tail.isEmpty) return this.document.getText(block);
195+
193196
return (
194197
this.document.getText(head) +
195-
this.sortBlocks(blocks, sort, sortChildren - 1).join("\n") +
198+
this.sortBlocks(blocks, sort, sortChildren - 1).join('\n') +
196199
this.document.getText(tail)
197200
);
198201
}
@@ -211,19 +214,18 @@ export default class BlockSortProvider {
211214
this.applySort(headers, sort);
212215
lines = [...headers, ...lines];
213216

214-
return lines.join("\n");
217+
return lines.join('\n');
215218
}
216219

217220
private applySort(blocks: string[], sort: (a: string, b: string) => number = BlockSortProvider.sort.asc) {
218-
blocks.sort((a, b) =>
219-
this.stringProcessor.isForceFirstBlock(a) || this.stringProcessor.isForceLastBlock(b)
221+
blocks.sort((a, b) => {
222+
const sanitizedA = this.stringProcessor.stripDecorators(this.stringProcessor.stripComments(a)).trim() || a.trim();
223+
const sanitizedB = this.stringProcessor.stripDecorators(this.stringProcessor.stripComments(b)).trim() || b.trim();
224+
return this.stringProcessor.isForceFirstBlock(sanitizedA) || this.stringProcessor.isForceLastBlock(sanitizedB)
220225
? -1
221-
: this.stringProcessor.isForceLastBlock(a)
226+
: this.stringProcessor.isForceLastBlock(sanitizedA) || this.stringProcessor.isForceFirstBlock(sanitizedB)
222227
? 1
223-
: sort(
224-
this.stringProcessor.stripDecorators(this.stringProcessor.stripComments(a)).trim() || a.trim(),
225-
this.stringProcessor.stripDecorators(this.stringProcessor.stripComments(b)).trim() || b.trim()
226-
)
227-
);
228+
: sort(sanitizedA, sanitizedB);
229+
});
228230
}
229231
}
Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { workspace } from "vscode";
2-
import { FoldingMarkerDefault, FoldingMarkerList } from "./StringProcessingProvider";
1+
import { workspace } from 'vscode';
2+
import { FoldingMarkerDefault, FoldingMarkerList } from './StringProcessingProvider';
33

44
const defaultFoldingMarkers: FoldingMarkerList<FoldingMarkerDefault> = {
5-
"()": { start: "\\(", end: "\\)" },
6-
"[]": { start: "\\[", end: "\\]" },
7-
"{}": { start: "\\{", end: "\\}" },
8-
"<>": { start: "<[a-zA-Z0-9\\-_=\\s]+", end: "<\\/[a-zA-Z0-9\\-_=\\s]+" },
5+
'()': { start: '\\(', end: '\\)' },
6+
'[]': { start: '\\[', end: '\\]' },
7+
'{}': { start: '\\{', end: '\\}' },
8+
'<>': { start: '<[a-zA-Z0-9\\-_=\\s]+', end: '<\\/[a-zA-Z0-9\\-_=\\s]+' },
99
};
1010

11-
const defaultCompleteBlockMarkers = ["\\}", "<\\/[a-zA-Z0-9\\-_=\\s]+"];
11+
const defaultCompleteBlockMarkers = ['\\}', '<\\/[a-zA-Z0-9\\-_=\\s]+'];
1212

1313
const defaultIndentIgnoreMarkers = [
14-
"{",
14+
'{',
1515
// eslint-disable-next-line quotes
1616
"end(?:for(?:each)?|if|while|case|def)?\\s*?([\\.\\[\\->\\|\\s]\\s*(?:[$A-Za-z0-9_+\\-\\*\\/\\^\\%\\<\\>\\=\\!\\?\\:]*|'[^']*?'|'[']*?'|\"[^\"]*?\"|`[^`]*?`)\\s*[\\]\\|]?\\s*)*",
17-
"esac|fi",
17+
'esac|fi',
1818
];
1919

2020
export interface NaturalSortOptions {
@@ -26,65 +26,66 @@ export interface NaturalSortOptions {
2626

2727
export default class ConfigurationProvider {
2828
public static getFoldingMarkers(): FoldingMarkerList {
29-
const additional: FoldingMarkerList = workspace.getConfiguration("blocksort").get("foldingMarkers") || {};
29+
const additional: FoldingMarkerList = workspace.getConfiguration('blocksort').get('foldingMarkers') || {};
3030
return { ...additional, ...defaultFoldingMarkers };
3131
}
3232

3333
public static getCompleteBlockMarkers(): string[] {
34-
const additional: string[] = workspace.getConfiguration("blocksort").get("completeBlockMarkers") || [];
34+
const additional: string[] = workspace.getConfiguration('blocksort').get('completeBlockMarkers') || [];
3535
return [...additional, ...defaultCompleteBlockMarkers];
3636
}
3737

3838
public static getSortConsecutiveBlockHeaders(): boolean {
3939
const configuration: boolean | undefined = workspace
40-
.getConfiguration("blocksort")
41-
.get("sortConsecutiveBlockHeaders");
40+
.getConfiguration('blocksort')
41+
.get('sortConsecutiveBlockHeaders');
4242
return configuration === undefined ? true : configuration;
4343
}
4444

4545
public static getDefaultMultilevelDepth(): number {
46-
const configuration: number | undefined = workspace.getConfiguration("blocksort").get("defaultMultilevelDepth");
46+
const configuration: number | undefined = workspace.getConfiguration('blocksort').get('defaultMultilevelDepth');
4747
return configuration === undefined ? -1 : configuration;
4848
}
4949

5050
public static getAskForMultilevelDepth(): boolean {
51-
const configuration: boolean | undefined = workspace.getConfiguration("blocksort").get("askForMultilevelDepth");
51+
const configuration: boolean | undefined = workspace.getConfiguration('blocksort').get('askForMultilevelDepth');
5252
return configuration === undefined ? true : configuration;
5353
}
5454

5555
public static getForceBlockHeaderFirstRegex(): string {
56-
return "$^";
56+
return '$^';
5757
}
5858

5959
public static getForceBlockHeaderLastRegex(): string {
60-
return "(default|else)\\s*('([^']|(?<=\\\\)')*'|\"([^\"]|(?<=\\\\)\")*\"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?(\r?\n|$)";
60+
return '^(\\s*(when|case)\\s*(\'([^\']|(?<=\\\\)\')*\'|"([^"]|(?<=\\\\)")*"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?\\n?\\r?)*\\s*default|else(?!\\s?if)\\s*:?$';
6161
}
6262

6363
public static getMultiBlockHeaderRegex(): string {
64-
return "(when|case|else|default)\\s*('([^']|(?<=\\\\)')*'|\"([^\"]|(?<=\\\\)\")*\"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?$";
64+
return '^(when|case|default|else)\\s*(\'([^\']|(?<=\\\\)\')*\'|"([^"]|(?<=\\\\)")*"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?$';
6565
}
6666

6767
public static getIncompleteBlockRegex(): string {
68-
return "(if|when|else|case|for|foreach|else|elsif|while|def|then)\\s*('([^']|(?<=\\\\)')*'|\"([^\"]|(?<=\\\\)\")*\"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?$";
68+
return '(if|when|else|case|for|foreach|else|elsif|while|def|then|default)\\s*(\'([^\']|(?<=\\\\)\')*\'|"([^"]|(?<=\\\\)")*"|`([^`]|(?<=\\\\)`)*`|[A-Za-z_+\\-*/%<>d.,s]*)*\\s*(.*:)?$';
6969
}
7070

7171
public static getIndentIgnoreMarkers(): string[] {
72-
const additional: string[] = workspace.getConfiguration("blocksort").get("indentIgnoreMarkers") || [];
72+
const additional: string[] = workspace.getConfiguration('blocksort').get('indentIgnoreMarkers') || [];
7373
return [...additional, ...defaultIndentIgnoreMarkers];
7474
}
7575

7676
public static getNaturalSortOptions(): NaturalSortOptions {
77-
const configuration: Partial<NaturalSortOptions> = workspace.getConfiguration("blocksort").get("naturalSorting") || {};
77+
const configuration: Partial<NaturalSortOptions> =
78+
workspace.getConfiguration('blocksort').get('naturalSorting') || {};
7879
return {
7980
enabled: false,
8081
padding: 9,
8182
omitUuids: false,
8283
sortNegativeValues: true,
83-
...configuration
84+
...configuration,
8485
};
8586
}
8687

8788
public static getEnableNaturalSorting(): boolean {
88-
return !!workspace.getConfiguration("blocksort").get("enableNaturalSorting");
89+
return !!workspace.getConfiguration('blocksort').get('enableNaturalSorting');
8990
}
9091
}

src/providers/StringProcessingProvider.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,27 +110,35 @@ export default class StringProcessingProvider {
110110
return new RegExp(completeBlockRegex, 'g').test(block);
111111
}
112112

113-
public isInCompleteBlock(block: string): boolean {
113+
public isIncompleteBlock(block: string): boolean {
114114
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
115-
const blockHeaderRegex = ConfigurationProvider.getIncompleteBlockRegex().replace(/\$$/, `(?:${comment}|\\s*)*$`);
116-
return new RegExp(blockHeaderRegex, 'g').test(block);
115+
const incompleteBlockRegex = ConfigurationProvider.getIncompleteBlockRegex()
116+
.replace(/\$$/, `(?:${comment}|\\s*)*$`)
117+
.replace(/^\^/, `^(?:${comment}|\\s*)*`);
118+
return new RegExp(incompleteBlockRegex, 'g').test(block);
117119
}
118120

119121
public isMultiBlockHeader(block: string): boolean {
120122
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
121-
const blockHeaderRegex = ConfigurationProvider.getMultiBlockHeaderRegex().replace(/\$$/, `(?:${comment}|\\s*)*$`);
123+
const blockHeaderRegex = ConfigurationProvider.getMultiBlockHeaderRegex()
124+
.replace(/\$$/, `(?:${comment}|\\s*)*$`)
125+
.replace(/^\^/, `^(?:${comment}|\\s*)*`);
122126
return new RegExp(blockHeaderRegex, 'g').test(block);
123127
}
124128

125129
public isForceFirstBlock(block: string): boolean {
126130
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
127-
const firstRegex = ConfigurationProvider.getForceBlockHeaderFirstRegex().replace(/\$$/, `(?:${comment}|\\s*)*$`);
131+
const firstRegex = ConfigurationProvider.getForceBlockHeaderFirstRegex()
132+
.replace(/\$$/, `(?:${comment}|\\s*)*\\n`)
133+
.replace(/^\^/, `^(?:${comment}|\\s*)*`);
128134
return new RegExp(firstRegex, 'g').test(block);
129135
}
130136

131137
public isForceLastBlock(block: string): boolean {
132138
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
133-
const lastRegex = ConfigurationProvider.getForceBlockHeaderLastRegex().replace(/\$$/, `(?:${comment}|\\s*)*$`);
139+
const lastRegex = ConfigurationProvider.getForceBlockHeaderLastRegex()
140+
.replace(/\$$/, `(?:${comment}|\\s*)*\\n`)
141+
.replace(/^\^/, `^(?:${comment}|\\s*)*`);
134142
return new RegExp(lastRegex, 'g').test(block);
135143
}
136144

src/test/fixtures/multilevel.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ export const multilevelSortTests: SortTest[] = [
77
compareFile: 'multilevel.txt.expect',
88
ranges: [new Range(0, 0, 16, 8)],
99
},
10+
{
11+
file: 'multilevel.ts.fixture',
12+
compareFile: 'multilevel.ts.expect',
13+
ranges: [new Range(1, 0, 18, 20)],
14+
},
1015
];
Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
1-
import * as assert from "assert";
2-
import { join } from "path";
3-
import { window, workspace, Selection } from "vscode";
4-
import BlockSortProvider from "../../providers/BlockSortProvider";
5-
import { expandTests, fixtureDir, sortTests, multilevelSortTests } from "../fixtures";
6-
import { SortTest } from "./types";
7-
import { naturalSortTests } from "../fixtures/natural";
1+
import * as assert from 'assert';
2+
import { join } from 'path';
3+
import { window, workspace, Selection } from 'vscode';
4+
import BlockSortProvider from '../../providers/BlockSortProvider';
5+
import { expandTests, fixtureDir, sortTests, multilevelSortTests } from '../fixtures';
6+
import { SortTest } from './types';
7+
import { naturalSortTests } from '../fixtures/natural';
88

99
function sortTest(
1010
tests: SortTest[],
11-
title = "Sort Blocks",
11+
title = 'Sort Blocks',
1212
sort: (a: string, b: string) => number = BlockSortProvider.sort.asc,
1313
sortChildren = 0
1414
) {
1515
tests.forEach(({ file, compareFile, ranges }) => {
1616
ranges.forEach((range, i) => {
1717
const descriptor = file.match(/(.*)\.(.*)\.fixture/);
18-
const [_, type, lang] = descriptor || ["", "generic", "generic"];
18+
const [_, type, lang] = descriptor || ['', 'generic', 'generic'];
1919
test(`Sort Blocks (${type}, lang ${lang}) #${i}`, async () => {
2020
const compareDocument = await workspace.openTextDocument(join(fixtureDir, compareFile));
2121
const document = await workspace.openTextDocument(join(fixtureDir, file));
2222
const blockSortProvider = new BlockSortProvider(document);
2323

2424
const blocks = blockSortProvider.getBlocks(range);
25-
const sorted = blockSortProvider.sortBlocks(blocks, sort, sortChildren).join("\n");
25+
const sorted = blockSortProvider.sortBlocks(blocks, sort, sortChildren).join('\n');
2626
const compareSorted = compareDocument.getText(range);
2727

28-
assert.strictEqual(sorted, compareSorted, "sorted ranges are not equal");
28+
assert.strictEqual(sorted, compareSorted, 'sorted ranges are not equal');
2929
});
3030
});
3131
});
3232
}
3333

34-
suite("Unit Suite for BlockSortProvider", async () => {
35-
window.showInformationMessage("Start tests for BlockSortProvider.");
34+
suite('Unit Suite for BlockSortProvider', async () => {
35+
window.showInformationMessage('Start tests for BlockSortProvider.');
3636

3737
expandTests.forEach(({ file, ranges, targetRanges }) => {
3838
ranges
3939
.map((range, i) => ({ position: range, target: targetRanges[i] }))
4040
.forEach(({ position, target }, i) => {
41-
const [_, lang] = file.match(/\.(.*)\.fixture/) || ["", "generic"];
41+
const [_, lang] = file.match(/\.(.*)\.fixture/) || ['', 'generic'];
4242
test(`Expands selection (lang ${lang}) #${i}`, async () => {
4343
const document = await workspace.openTextDocument(join(fixtureDir, file));
4444
const blockSortProvider = new BlockSortProvider(document);
4545
const selection = new Selection(position.start, position.end);
4646
const expanded = blockSortProvider.expandSelection(selection);
4747

48-
assert.deepStrictEqual(expanded, target, "range did not expand correctly");
48+
assert.deepStrictEqual(expanded, target, 'range did not expand correctly');
4949
});
5050
});
5151
});
5252

53-
sortTest(sortTests, "Sort Blocks");
54-
sortTest(multilevelSortTests, "Deep Sort Blocks", BlockSortProvider.sort.asc, -1);
55-
sortTest(naturalSortTests, "Natural Sort Blocks", BlockSortProvider.sort.ascNatural, 0);
53+
sortTest(sortTests, 'Sort Blocks');
54+
sortTest(multilevelSortTests, 'Deep Sort Blocks', BlockSortProvider.sort.asc, -1);
55+
sortTest(naturalSortTests, 'Natural Sort Blocks', BlockSortProvider.sort.ascNatural, 0);
5656
});

test/fixtures/multilevel.ts.expect

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
function test(input: string, select = 0) {
2+
switch(input) {
3+
case '1':
4+
return 'first';
5+
case '2':
6+
return 'second';
7+
case '3':
8+
switch (select) {
9+
case 1:
10+
return 'third-first';
11+
case 2:
12+
return 'third-second';
13+
case 8:
14+
return 'third-third';
15+
default:
16+
return 'third-last';
17+
}
18+
default:
19+
return 'last';
20+
}
21+
}

0 commit comments

Comments
 (0)