Skip to content

Commit

Permalink
feat: simplify and extend folding detection
Browse files Browse the repository at this point in the history
  • Loading branch information
1nVitr0 committed Apr 15, 2021
1 parent f2b8ce1 commit 27c4769
Showing 1 changed file with 69 additions and 43 deletions.
112 changes: 69 additions & 43 deletions src/providers/StringProcessingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,48 @@ import { Range, TextDocument, workspace } from 'vscode';
import { commentMarkers, commentRegex } from '../constants/comments';
import { stringMarkers } from '../constants/strings';

type FoldingMarker = '()' | '[]' | '{}';
export type Folding = Partial<Record<FoldingMarker | '<>', number | null>> & Record<FoldingMarker, number | null>;
type FoldingMarkerDefault = '()' | '[]' | '{}' | '<>';
type FoldingMarkerList<T extends string = string> = Record<
T,
{ start: string; end: string; abortOnCurlyBrace?: boolean }
>;
export interface FoldingLevel {
level: number;
changed?: boolean;
committed?: boolean;
indent?: number;
}
export type Folding<T extends string = string> = { [marker in keyof FoldingMarkerList<T>]: FoldingLevel };

type FoldingMarkerOld = '()' | '[]' | '{}';
export type FoldingOld = Partial<Record<FoldingMarkerOld | '<>', number | null>> &
Record<FoldingMarkerOld, number | null>;

const foldingMarkers: FoldingMarkerList = {
'()': { start: '\\(', end: '\\)' },
'[]': { start: '\\[', end: '\\]' },
'{}': { start: '\\{', end: '\\}' },
'<>': { start: '<[a-zA-Z0-9\\-_=\\s]+', end: '<\\/[a-zA-Z0-9\\-_=\\s]+' },
};

const completeBlockMarkers = ['\\}', '<\\/[a-zA-Z0-9\\-_=\\s]+'];

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

function initialFolding(): Folding {
return Object.keys(foldingMarkers).reduce<Folding>((r, key) => {
r[key] = { level: 0 };
return r;
}, {});
}

export default class StringProcessingProvider {
private static foldingMarkers: FoldingMarker[] = ['()', '[]', '{}'];
private static foldingMarkers: FoldingMarkerOld[] = ['()', '[]', '{}'];
private static useXmlFolding = ['html', 'jsx', 'xml'];

private document: TextDocument;
Expand All @@ -22,15 +59,15 @@ export default class StringProcessingProvider {
return (line.match(/^\s*/)?.pop() || '').length / indentWidth;
}

public getIndentRange(text: string): { min: number; max: number } {
public getIndentRange(text: string, checkIndentIgnore = true): { min: number; max: number } {
const lines = text.split(/\r?\n/);
const indentWidth: number = workspace.getConfiguration('editor').get('tabSize') || 4;

let min = Infinity;
let max = 0;

for (const line of lines) {
if (this.isIndentIgnoreLine(line)) continue;
if (checkIndentIgnore && this.isIndentIgnoreLine(line)) continue;
const indent = this.getIndent(line, indentWidth);
if (indent < min) min = indent;
if (indent > max) max = indent;
Expand All @@ -39,42 +76,37 @@ export default class StringProcessingProvider {
return { min, max };
}

public getFolding(
line: string,
initial: Folding = { '()': null, '[]': null, '{}': null },
validate = false
): Folding {
const result: Folding = {
'()': initial['()'],
'[]': initial['[]'],
'{}': initial['{}'],
'<>': initial['<>'] || null,
};
const foldingStart = StringProcessingProvider.foldingMarkers.map((marker) => marker[0]);
const foldingEnd = StringProcessingProvider.foldingMarkers.map((marker) => marker[1]);

let foldingIndex: number = -1;
for (const char of this.stripStrings(this.stripComments(line)).trim()) {
if ((foldingIndex = foldingStart.indexOf(char)) >= 0) {
const marker = (foldingStart[foldingIndex] + foldingEnd[foldingIndex]) as FoldingMarker;
result[marker] = (result[marker] || 0) + 1;
} else if ((foldingIndex = foldingEnd.indexOf(char)) >= 0) {
const marker = (foldingStart[foldingIndex] + foldingEnd[foldingIndex]) as FoldingMarker;
if (!validate || (result[marker] || 0) > 0) result[marker] = (result[marker] || 1) - 1;
}
}
public getFolding(text: string, initial: Folding = initialFolding(), validate = false): Folding {
const result: Folding = { ...initial };

const lines = text.split(/\r?\n/);
let markerKeys = Object.keys(foldingMarkers);
markerKeys.splice(markerKeys.indexOf('{}'), 1);
markerKeys.push('{}'); // ensure '{}' is last to make abort on curly braces possible

result['<>'] = this.getXmlFolding(line, result['<>'] || 0, validate);
for (const line of lines) {
const sanitized = this.stripStrings(this.stripComments(line)).trim();
for (const key of Object.keys(foldingMarkers)) {
const folding = result[key] || { level: 0 };
const { start, end, abortOnCurlyBrace } = foldingMarkers[key];

const open = sanitized.split(new RegExp(start)).length - 1;
const close = sanitized.split(new RegExp(end)).length - 1;

folding.level += open - close;
}
}
return result;
}

public hasFolding(folding: Folding): boolean {
return !!(folding['()'] || folding['[]'] || folding['{}'] || folding['<>']);
for (const key of Object.keys(folding)) if (folding[key].level) return true;

return false;
}

public totalOpenFolding(folding: Folding): number {
return (folding['()'] || 0) + (folding['[]'] || 0) + (folding['{}'] || 0);
return Object.keys(folding).reduce((total, key) => total + folding[key].level, 0);
}

public isList(blocks: Range[]): boolean {
Expand All @@ -86,12 +118,14 @@ export default class StringProcessingProvider {

public isIndentIgnoreLine(line: string): boolean {
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
return new RegExp(`^\\s*{\\s*(?:${comment}\\s*)*\\s*$`).test(line);
const indentIgnoreRegex = `^\\s*(?:${indentIgnoreMarkers.join('|')})(?:${comment}|\\s*)*$`;
return new RegExp(indentIgnoreRegex).test(line);
}

public isClosedBlock(block: string): boolean {
public isCompleteBlock(block: string): boolean {
const comment = commentRegex[this.document.languageId || 'default'] || commentRegex.default;
return new RegExp(`(}|</[a-zA-Z0-9\-_]+>)(,|;)?\\s*(${comment}\\s*)*\\s*$`, 'g').test(block);
const completeBlockRegex = `(?:${completeBlockMarkers.join('|')})(?:,|;)?(?:${comment}|\\s*)*(?:,|;)?$`;
return new RegExp(completeBlockRegex, 'g').test(block);
}

public isValidLine(line: string): boolean {
Expand Down Expand Up @@ -138,12 +172,4 @@ export default class StringProcessingProvider {

return result;
}

private getXmlFolding(line: string, initial: number | null = null, validate = false): number {
const stripped = this.stripComments(this.stripStrings(line));
const open = stripped.match(/<[a-zA-Z0-9\-_=\s]+/g)?.length || 0;
const close = stripped.match(/<\/[a-zA-Z0-9\-_=\s]+/g)?.length || 0;

return (initial || 0) + (validate ? Math.max(open - close, 0) : open - close);
}
}

0 comments on commit 27c4769

Please sign in to comment.