diff --git a/tools/bend-fmt-lsp/src/formatter.ts b/tools/bend-fmt-lsp/src/formatter.ts index 27129c6be..d2ef54bae 100644 --- a/tools/bend-fmt-lsp/src/formatter.ts +++ b/tools/bend-fmt-lsp/src/formatter.ts @@ -167,11 +167,13 @@ type BodyScope = { open: number; angle: number; end: number }; type InlineBody = { at: number; scope?: BodyScope }; // The tokens that start a body on the line of its `do`, `match`, `case` or -// `def` header, `=>` lambda opener, `;` separator or `(` group opener. As in -// `do IO: a`, Bend pins the lines after it to the column of `a`. +// `def` header, `=>` lambda opener, `;` separator, `(` group opener or +// parenthesized `,` separator. As in `do IO: a`, Bend pins the lines +// after it to the column of `a`. function inlineBodies(lines: Line[]): InlineBody[][] { let open = 0; const angles = [0]; + const parens: number[] = []; const bodyScopes: BodyScope[] = []; let header: { open: number; angle: number; typed: boolean } | null = null; return lines.map((line, row) => line.tokens.flatMap((token, index): InlineBody[] => { @@ -183,11 +185,13 @@ function inlineBodies(lines: Line[]): InlineBody[][] { } else if (["(", "[", "{"].includes(token.text)) { open++; angles.push(0); + if (token.text === "(") parens.push(open); if (token.text === "(" && index + 1 < line.tokens.length) { bodyScopes.push({ open, angle: angles[open], end: lines.length }); return [{ at: index + 1, scope: bodyScopes.at(-1) }]; } } else if ([")", "]", "}"].includes(token.text)) { + if (parens.at(-1) === open) parens.pop(); if (open > 0) angles.pop(); open = Math.max(0, open - 1); } else if (ANGLES.has(token.text)) { @@ -209,6 +213,10 @@ function inlineBodies(lines: Line[]): InlineBody[][] { // A leading closer/separator has no body statement to pin on this line. bodyScopes.pop()!.end = row - (index === 0 ? 1 : 0); } + if (token.text === "," && parens.at(-1) === open && angles[open] === 0 && index + 1 < line.tokens.length) { + bodyScopes.push({ open, angle: angles[open], end: lines.length }); + return [{ at: index + 1, scope: bodyScopes.at(-1) }]; + } return []; })); } diff --git a/tools/bend-fmt-lsp/src/test/formatter.test.ts b/tools/bend-fmt-lsp/src/test/formatter.test.ts index 33d70a432..6b7f1ec7a 100644 --- a/tools/bend-fmt-lsp/src/test/formatter.test.ts +++ b/tools/bend-fmt-lsp/src/test/formatter.test.ts @@ -8,7 +8,8 @@ import { fileURLToPath } from "node:url"; import { formatBend } from "../formatter.js"; // Bend pins the lines after a body that shares a line with its `do`, `match`, -// `case` or `def` header, `=>` lambda opener or `;` to that body's column. [source, formatted] +// `case` or `def` header, `=>` lambda opener, `;`, `(` or parenthesized `,` +// to that body's column. [source, formatted] const pinned = [ [ "import Base\ndef f(a:Array)->Array: b=a; b[0]<-7\n b[1]<-8\n\ndef main() -> Array:\n f([0 : U32 * 2n])\n", @@ -76,6 +77,14 @@ const pinned = [ "import Base\ndef f(a: Array, b: Bool, c: Bool) -> Array:\n match b:\n case True{}: match c:\n case True{}: a[0] <- 7\n a[1] <- 8\n case False{}: a\n case False{}: a\n\ndef main() -> Array:\n f([0 : U32*2n], True{}, True{})\n", "import Base\ndef f(a: Array, b: Bool, c: Bool) -> Array:\n match b:\n case True{}: match c:\n case True{}: a[0] <- 7\n a[1] <- 8\n case False{}: a\n case False{}: a\n\ndef main() -> Array:\n f([0 : U32 * 2n], True{}, True{})\n", ], + [ + "import Base\ndef main()->U32 & Array:\n a = [0 : U32 * 2n]\n (17,a[0]<-3\n a[1]<-5)\n", + "import Base\ndef main() -> U32 & Array:\n a = [0 : U32 * 2n]\n (17, a[0] <- 3\n a[1] <- 5)\n", + ], + [ + "import Base\ndef finish(p:U32 & Array,n:U32)->Array:\n (x,a) = p\n a[0] <- U32.add(x,n)\ndef main()->Array:\n a = [0 : U32 * 2n]\n finish((17,a[0]<-3\n a[1]<-5),\n 11)\n", + "import Base\ndef finish(p: U32 & Array, n: U32) -> Array:\n (x, a) = p\n a[0] <- U32.add(x, n)\ndef main() -> Array:\n a = [0 : U32 * 2n]\n finish((17, a[0] <- 3\n a[1] <- 5),\n 11)\n", + ], ]; test("moves pinned lines as far as the body they line up with", () => {