Skip to content

Commit 3cbaa4f

Browse files
Add support for .editorconfig files
1 parent c1bcd19 commit 3cbaa4f

15 files changed

Lines changed: 309 additions & 324 deletions

README.md

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,27 @@ tree-sitter-fmt [options] [file/dir/glob ...]
1212

1313
### Options
1414

15-
All binaries support these global options:
15+
All binaries support these options:
1616

1717
| Option | Meaning |
1818
| ----------- | -------------------------------- |
1919
| `--help` | Show help |
2020
| `--version` | Show version |
2121
| `--check` | Check formatting without writing |
2222

23-
`talon-fmt` also supports:
23+
Formatting options are read from [.editorconfig](https://editorconfig.org) based on the file path being
24+
formatted. For stdin, the formatter resolves a synthetic file such as
25+
`stdin.talon`, `stdin.talon-list`, `stdin.scm`, or `stdin.snippet` from the
26+
current working directory and loads `.editorconfig` relative to that path.
2427

25-
| Option | Meaning | Default |
26-
| -------------------- | -------------------------------- | ------- |
27-
| `--indent-tabs` | Use tabs for indentation | |
28-
| `--indent-width <n>` | Set indentation width | `4` |
29-
| `--line-width <n>` | Set preferred maximum line width | `80` |
30-
| `--column-width <n>` | Set aligned left-column width | |
28+
Supported `.editorconfig` properties:
3129

32-
`tree-sitter-fmt` also supports:
33-
34-
| Option | Meaning | Default |
35-
| -------------------- | ------------------------ | ------- |
36-
| `--indent-tabs` | Use tabs for indentation | |
37-
| `--indent-width <n>` | Set indentation width | `4` |
38-
39-
`snippet-fmt` does not support any additional formatter-specific options.
30+
| Property | Meaning |
31+
| ----------------- | ---------------------------------- |
32+
| `indent_style` | Use tabs or spaces for indentation |
33+
| `indent_size` | Set indentation width |
34+
| `max_line_length` | Set preferred maximum line width |
35+
| `column_width` | Set aligned left-column width |
4036

4137
Use `--` to mark the end of options. Any following arguments are treated as
4238
file, directory, or glob patterns even if they start with `--`.

package-lock.json

Lines changed: 34 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
},
4545
"dependencies": {
4646
"@cursorless/tree-sitter-wasms": "^0.7.0",
47+
"editorconfig": "^3.0.2",
4748
"fast-glob": "^3.3.3",
4849
"get-stdin": "^10.0.0",
4950
"talon-snippets": "^1.3.0",

src/cli/cli.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import getStdin from "get-stdin";
22
import * as fs from "node:fs/promises";
3+
import * as path from "node:path";
34
import * as process from "node:process";
45
import type { Readable } from "node:stream";
5-
import type { CLI, Options } from "../types.js";
6+
import type { CLI } from "../types.js";
67
import { EXIT_ERROR, EXIT_FAIL, EXIT_OK } from "../util/constants.js";
78
import { getErrorMessage } from "../util/getErrorMessage.js";
9+
import { getOptionsFromConfig } from "../util/getOptionsFromConfig.js";
810
import { isMissingFileError } from "../util/isMissingFileError.js";
911
import { parseArgs } from "../util/parseArgs.js";
1012
import { parseFilePatterns } from "../util/parseFilePatterns.js";
@@ -22,7 +24,7 @@ export async function main(cli: CLI): Promise<void> {
2224
}
2325

2426
async function mainUnsafe(cli: CLI): Promise<number> {
25-
const args = parseArgs(cli, process.argv.slice(2));
27+
const args = parseArgs(process.argv.slice(2));
2628

2729
if (args.help) {
2830
printHelp(cli);
@@ -37,13 +39,13 @@ async function mainUnsafe(cli: CLI): Promise<number> {
3739
const hasFilePatterns = args.filePatterns.length > 0;
3840

3941
if (hasFilePatterns) {
40-
return mainFormatFiles(cli, args.check, args, args.filePatterns);
42+
return mainFormatFiles(cli, args.check, args.filePatterns);
4143
}
4244

4345
// If no file patterns are provided, check if there's input from stdin.
4446
// If stdin TTY it's an interactive terminal, so we shouldn't read from it.
4547
if (!process.stdin.isTTY) {
46-
return mainFormatStdin(cli, process.stdin, args.check, args);
48+
return mainFormatStdin(cli, process.stdin, args.check);
4749
}
4850

4951
throw new Error(
@@ -54,15 +56,14 @@ async function mainUnsafe(cli: CLI): Promise<number> {
5456
async function mainFormatFiles(
5557
cli: CLI,
5658
check: boolean,
57-
options: Options,
5859
filePatterns: string[],
5960
): Promise<number> {
6061
if (check) {
6162
console.log("Checking formatting...");
6263
}
6364

6465
const filePaths = await parseFilePatterns(cli, filePatterns);
65-
const changedFileCount = await formatFiles(cli, check, options, filePaths);
66+
const changedFileCount = await formatFiles(cli, check, filePaths);
6667

6768
if (check) {
6869
if (changedFileCount > 0) {
@@ -88,13 +89,12 @@ async function mainFormatFiles(
8889
export async function formatFiles(
8990
cli: CLI,
9091
check: boolean,
91-
options: Options,
9292
filePaths: string[],
9393
): Promise<number> {
9494
let changedFileCount = 0;
9595

9696
for (const fileName of filePaths) {
97-
if (await formatFile(cli, check, options, fileName)) {
97+
if (await formatFile(cli, check, fileName)) {
9898
changedFileCount++;
9999
}
100100
}
@@ -105,10 +105,10 @@ export async function formatFiles(
105105
export async function formatFile(
106106
cli: CLI,
107107
check: boolean,
108-
options: Options,
109108
filePath: string,
110109
): Promise<boolean> {
111110
try {
111+
const options = await getOptionsFromConfig(filePath);
112112
const content = await fs.readFile(filePath, "utf8");
113113
const formatted = await cli.format(content, options, filePath);
114114

@@ -142,10 +142,13 @@ export async function mainFormatStdin(
142142
cli: CLI,
143143
stdin: Readable,
144144
check: boolean,
145-
options: Options,
146145
): Promise<number> {
147146
const input = await getStdin({ stdin });
148-
const formatted = await cli.format(input, options, "stdin");
147+
const fileEnding = cli.getStdinFileEnding(input);
148+
const fileName = `stdin.${fileEnding}`;
149+
const filePath = path.resolve(fileName);
150+
const options = await getOptionsFromConfig(filePath);
151+
const formatted = await cli.format(input, options, filePath);
149152

150153
if (check) {
151154
if (input !== formatted) {

src/cli/snippetFormatter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import { snippetFormatter } from "../lib/snippetFormatter.js";
44
import { main } from "./cli.js";
55

6+
const fileEnding = "snippet";
7+
68
void main({
79
binName: "snippet-fmt",
8-
fileEndings: ["snippet"],
9-
supportedFlagArgs: [],
10-
supportedValueArgs: [],
10+
fileEndings: [fileEnding],
11+
12+
getStdinFileEnding() {
13+
return fileEnding;
14+
},
1115

1216
format: async (text) => {
1317
const updated = snippetFormatter(text);

src/cli/talonFormatter.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ import { talonFormatter } from "../lib/talonFormatter.js";
55
import { parseText } from "../util/parseText.js";
66
import { main } from "./cli.js";
77

8+
const fileEndingTalon = "talon";
9+
const fileEndingTalonList = "talon-list";
10+
811
void main({
912
binName: "talon-fmt",
10-
fileEndings: ["talon", "talon-list"],
11-
supportedFlagArgs: ["--indent-tabs"],
12-
supportedValueArgs: ["--indent-width", "--line-width", "--column-width"],
13+
fileEndings: [fileEndingTalon, fileEndingTalonList],
14+
15+
getStdinFileEnding(text) {
16+
return textIsList(text) ? fileEndingTalonList : fileEndingTalon;
17+
},
1318

14-
format: async (text, options, fileName) => {
15-
if (isListFile(text, fileName)) {
19+
format: async (text, options, filePath) => {
20+
if (isListFile(text, filePath)) {
1621
const updated = talonListFormatter(text, options);
1722
return Promise.resolve(updated);
1823
}
@@ -22,12 +27,16 @@ void main({
2227
},
2328
});
2429

25-
function isListFile(text: string, fileName: string): boolean {
26-
if (fileName.endsWith(".talon-list")) {
27-
return true;
28-
}
29-
if (fileName.endsWith(".talon")) {
30+
function isListFile(text: string, filePath: string): boolean {
31+
if (filePath.endsWith(".talon")) {
3032
return false;
3133
}
34+
if (filePath.endsWith(".talon-list")) {
35+
return true;
36+
}
37+
return textIsList(text);
38+
}
39+
40+
function textIsList(text: string): boolean {
3241
return text.startsWith("list:");
3342
}

src/cli/treeSitterFormatter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import { treeSitterFormatter } from "../lib/treeSitterFormatter.js";
44
import { parseText } from "../util/parseText.js";
55
import { main } from "./cli.js";
66

7+
const fileEnding = "scm";
8+
79
void main({
810
binName: "tree-sitter-fmt",
9-
fileEndings: ["scm"],
10-
supportedFlagArgs: ["--indent-tabs"],
11-
supportedValueArgs: ["--indent-width"],
11+
fileEndings: [fileEnding],
12+
13+
getStdinFileEnding() {
14+
return fileEnding;
15+
},
1216

1317
format: async (text, options) => {
1418
const node = await parseText(text, "tree-sitter-query");

0 commit comments

Comments
 (0)