Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ foo bar baz: "foo bar baz"
```yaml
repos:
- repo: https://github.com/cursorless-dev/talon-tools
rev: v0.13.0
rev: v0.13.1
hooks:
- id: talon-fmt
- id: snippet-fmt
Expand Down
4 changes: 2 additions & 2 deletions dist/lib.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/lib.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/snippetFormatter.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/snippetFormatter.js.map

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions dist/talonFormatter.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/talonFormatter.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/treeSitterFormatter.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/treeSitterFormatter.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cursorless/talon-tools",
"version": "0.13.0",
"version": "0.13.1",
"description": "Linting and formatting tools for Talon and Cursorless",
"author": "Cursorless Dev",
"license": "MIT",
Expand Down
98 changes: 67 additions & 31 deletions src/talon/talonFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class TalonFormatter {
return node.children.map((n) => this.getNodeText(n)).join("");

case "string":
return formatString(node);
return formatString(node, this.eol);

case "match_modifier":
case "for":
Expand Down Expand Up @@ -283,40 +283,78 @@ class TalonFormatter {

private addLeftRightNode(node: SyntaxNode, forceMultiline: boolean): void {
const [leftNode, _colonNode, ...rightNodes] = node.children;
const left = this.getNodeText(leftNode);

if (!forceMultiline && rightNodes.length === 1) {
if (
!this.preserveMultiline ||
isLeftRightSingleLine(leftNode, rightNodes)
) {
const rightNode = rightNodes[0];
if (
rightNode.children.length === 1 &&
!forceMultilineBody(rightNode.children[0])
) {
const right = this.getNodeText(rightNode.children[0]);
const leftWithPadding =
this.columnWidth == null
? `${left}: `
: `${left}: `.padEnd(this.columnWidth);
if (
leftWithPadding.length + right.length <=
this.maxLineLength
) {
this.lines.push(leftWithPadding + right);
return;
}
}
}
const singleLineText = forceMultiline
? undefined
: this.getSingleLineText(leftNode, rightNodes);

if (singleLineText != null) {
this.lines.push(singleLineText);
return;
}

const left = this.getNodeText(leftNode);
this.lines.push(`${left}:`);

for (const n of rightNodes) {
this.addNode(n, true);
}
}

private getSingleLineText(
leftNode: SyntaxNode,
rightNodes: SyntaxNode[],
): string | undefined {
// Multiple right nodes cannot be formatted on a single line.
if (rightNodes.length !== 1) {
return undefined;
}

// If preserveMultiline is enabled and the left and right nodes are not on the same line, do not format on a single line.
if (
this.preserveMultiline &&
!isLeftRightSingleLine(leftNode, rightNodes)
) {
return undefined;
}

const rightNode = rightNodes[0];

// If the right node has multiple children, it cannot be formatted on a single line.
if (rightNode.children.length !== 1) {
return undefined;
}

// If the right node's body should be forced to multiline, do not format on a single line.
if (forceMultilineBody(rightNode.children[0])) {
return undefined;
}

const right = this.getNodeText(rightNode.children[0]);

// The formatted value must be a single line
if (stringIsMultiLine(right)) {
return undefined;
}

const left = this.getNodeText(leftNode);

const leftWithPadding =
this.columnWidth == null
? `${left}: `
: `${left}: `.padEnd(this.columnWidth);

// If the combined length of the left part with padding and the right part exceeds the maximum line length, do not format on a single line.
if (leftWithPadding.length + right.length > this.maxLineLength) {
return undefined;
}

return `${leftWithPadding}${right}`;
}
}

function stringIsMultiLine(value: string): boolean {
return /[\r\n]/u.test(value);
}

// Returns true if the node should remain in the declaration block instead of
Expand All @@ -326,8 +364,6 @@ function forceMultilineBody(node: SyntaxNode): boolean {
case "if_statement":
case "for_statement":
return true;
case "expression_statement":
return node.startPosition.row !== node.endPosition.row;
default:
return false;
}
Expand All @@ -354,9 +390,9 @@ function isFirstChild(node: SyntaxNode): boolean {
return node.id === node.parent?.children[0].id;
}

function formatString(node: SyntaxNode): string {
// Convert single quotes to double quotes
const text = convertQuotes(node.text);
function formatString(node: SyntaxNode, eol: string): string {
// Convert single quotes to double quotes and normalize line endings
const text = convertQuotes(node.text).replaceAll(/\r?\n/gu, eol);

// A single string literal is allowed as syntactic sugar for the insert
// action, but not in combination with other sibling statements.
Expand Down
6 changes: 6 additions & 0 deletions src/test/talonFixtures/Multiline argument list.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
foo:
insert(
"x"
)
================ EXPECTED ================
foo: insert("x")
7 changes: 7 additions & 0 deletions src/test/talonFixtures/Multiline string argument.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
foo:
insert("""hello
world""")
================ EXPECTED ================
foo:
insert("""hello
world""")
15 changes: 15 additions & 0 deletions src/test/talonFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ suite("Talon formatter", () => {
assert.equal(actual, "foo: bar\nbaz: bongo\n");
});

test("CRLF string", async () => {
const actual = await talonFormatter('foo:\r\n """Hello\r\n world"""');
assert.equal(actual, 'foo:\n """Hello\n world"""\n');
});

test("CRLF comment", async () => {
const actual = await talonFormatter('# Hello\r\nfoo: "bar"');
assert.equal(actual, '# Hello\nfoo: "bar"\n');
Expand Down Expand Up @@ -83,6 +88,16 @@ suite("Talon formatter", () => {
assert.equal(actual, "aaa:\n bbb\n");
});

test("preserveMultiline: true keeps normalized multiline argument lists below the colon", async () => {
const actual = await talonFormatter(
'foo:\n insert(\n "x"\n )',
{
preserveMultiline: true,
},
);
assert.equal(actual, 'foo:\n insert("x")\n');
});

test("columnWidth: 10", async () => {
const actual = await talonFormatter("aaa:\n bbb", {
columnWidth: 10,
Expand Down
Loading