Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An additional parameter keepLines has been added into the formatting options which allows to keep the original line formatting #66

Merged
merged 9 commits into from
Jul 11, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
3.1.0 2022-07-07
==================
* added new API `FormattingOptions.keepLines` : It leaves the lines as is

3.0.0 2020-11-13
==================
* fixed API spec for `parseTree`. Can return `undefine` for empty input.
Expand Down
106 changes: 71 additions & 35 deletions src/impl/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export function format(documentText: string, range: Range | undefined, options:
}
let eol = getEOL(options, documentText);

let lineBreak = false;
let numberLineBreaks = 0;

let indentLevel = 0;
let indentValue: string;
if (options.insertSpaces) {
Expand All @@ -48,14 +49,24 @@ export function format(documentText: string, range: Range | undefined, options:
let scanner = createScanner(formatText, false);
let hasError = false;

function newLineAndIndent(): string {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
function newLinesAndIndent(): string {
if (numberLineBreaks > 1) {
return repeat(eol, numberLineBreaks) + repeat(indentValue, initialIndentLevel + indentLevel);
} else {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
}
}

function scanNext(): SyntaxKind {
let token = scanner.scan();
lineBreak = false;
numberLineBreaks = 0;

while (token === SyntaxKind.Trivia || token === SyntaxKind.LineBreakTrivia) {
lineBreak = lineBreak || (token === SyntaxKind.LineBreakTrivia);
if (token === SyntaxKind.LineBreakTrivia && options.keepLines) {
numberLineBreaks += 1;
} else if (token === SyntaxKind.LineBreakTrivia) {
numberLineBreaks = 1;
}
token = scanner.scan();
}
hasError = token === SyntaxKind.Unknown || scanner.getTokenError() !== ScanError.None;
Expand All @@ -69,6 +80,9 @@ export function format(documentText: string, range: Range | undefined, options:
}

let firstToken = scanNext();
if (options.keepLines && numberLineBreaks > 0) {
addEdit(repeat(eol, numberLineBreaks), 0, 0);
}

if (firstToken !== SyntaxKind.EOF) {
let firstTokenStart = scanner.getTokenOffset() + formatTextStart;
Expand All @@ -77,87 +91,109 @@ export function format(documentText: string, range: Range | undefined, options:
}

while (firstToken !== SyntaxKind.EOF) {

let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
let secondToken = scanNext();

let replaceContent = '';
let needsLineBreak = false;
while (!lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
// comments on the same line: keep them on the same line, but ignore them otherwise

while (numberLineBreaks === 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
let commentTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(' ', firstTokenEnd, commentTokenStart);
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
needsLineBreak = secondToken === SyntaxKind.LineCommentTrivia;
replaceContent = needsLineBreak ? newLineAndIndent() : '';
replaceContent = needsLineBreak ? newLinesAndIndent() : '';
secondToken = scanNext();
}

if (secondToken === SyntaxKind.CloseBraceToken) {
if (firstToken !== SyntaxKind.OpenBraceToken) {
indentLevel--;
replaceContent = newLineAndIndent();
if (firstToken !== SyntaxKind.OpenBraceToken) { indentLevel--; };

if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBraceToken) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
replaceContent = ' ';
}
} else if (secondToken === SyntaxKind.CloseBracketToken) {
if (firstToken !== SyntaxKind.OpenBracketToken) {
indentLevel--;
replaceContent = newLineAndIndent();
if (firstToken !== SyntaxKind.OpenBracketToken) { indentLevel--; };

if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBracketToken) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
replaceContent = ' ';
}
} else {
switch (firstToken) {
case SyntaxKind.OpenBracketToken:
case SyntaxKind.OpenBraceToken:
indentLevel++;
replaceContent = newLineAndIndent();
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think at that point, options.keepLines is always true -> can remove the check

replaceContent = ' ';
}
break;
case SyntaxKind.CommaToken:
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
replaceContent = ' ';
}
break;
case SyntaxKind.LineCommentTrivia:
replaceContent = newLineAndIndent();
replaceContent = newLinesAndIndent();
break;
case SyntaxKind.BlockCommentTrivia:
if (lineBreak) {
replaceContent = newLineAndIndent();
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
// symbol following comment on the same line: keep on same line, separate with ' '
replaceContent = ' ';
}
break;
case SyntaxKind.ColonToken:
if (!needsLineBreak) {
if (options.keepLines && numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
replaceContent = ' ';
}
break;
case SyntaxKind.StringLiteral:
if (secondToken === SyntaxKind.ColonToken) {
if (!needsLineBreak) {
replaceContent = '';
}
break;
if (options.keepLines && numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (secondToken === SyntaxKind.ColonToken && !needsLineBreak) {
replaceContent = '';
}
// fall through
break;
case SyntaxKind.NullKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NumericLiteral:
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken:
if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) {
if (!needsLineBreak) {
if (options.keepLines && numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
if ((secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) && !needsLineBreak) {
replaceContent = ' ';
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
break;
case SyntaxKind.Unknown:
hasError = true;
break;
}
if (lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
replaceContent = newLineAndIndent();
if (numberLineBreaks > 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
replaceContent = newLinesAndIndent();
}
}
if (secondToken === SyntaxKind.EOF) {
replaceContent = options.insertFinalNewline ? eol : '';
if (options.keepLines && numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = options.insertFinalNewline ? eol : '';
}
}
let secondTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(replaceContent, firstTokenEnd, secondTokenStart);
Expand Down Expand Up @@ -209,4 +245,4 @@ function getEOL(options: FormattingOptions, text: string): string {

export function isEOL(text: string, offset: number) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
}
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ export interface FormattingOptions {
* If set, will add a new line at the end of the document.
*/
insertFinalNewline?: boolean;
/**
* If true, will keep line positions as is in the formatting
*/
keepLines?: boolean;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/test/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ suite('JSON - edits', () => {
let formattingOptions: FormattingOptions = {
insertSpaces: true,
tabSize: 2,
eol: '\n'
eol: '\n',
keepLines: false
};

let options: ModificationOptions = {
Expand Down
Loading