Skip to content

Commit de9e102

Browse files
committed
Formatter update
Adds a formatter support for Ruff, two commands and related settings. Updates the config to handle workspace and global boolean settings. Resolves #3.
1 parent 6ae503f commit de9e102

4 files changed

Lines changed: 195 additions & 4 deletions

File tree

Scripts/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ class Config {
33
let global = nova.config.get(nova.extension.identifier + "." + key);
44
let workspace = nova.workspace.config.get(nova.extension.identifier + "." + key);
55

6+
if (typeof global === "boolean" && typeof workspace == "number") {
7+
if (workspace === -1) workspace = null
8+
else workspace = Boolean(workspace)
9+
}
10+
611
if (workspace !== null && global !== workspace) return workspace;
712
return global;
813
}

Scripts/formatter.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
class Formatter {
2+
constructor(config) {
3+
this.config = config;
4+
}
5+
6+
async getProcess(filename = null) {
7+
const executablePath = nova.path.expanduser(this.config.get("executablePath"));
8+
const commandArguments = this.config.get("commandFormatArguments");
9+
const defaultOptions = (filename)
10+
? (filename !== ".")
11+
? ["--quiet", `--stdin-filename=${filename}`, "-"]
12+
: ["--quiet", filename]
13+
: ["--quiet", "-"];
14+
15+
if (!nova.fs.stat(executablePath)) {
16+
console.error(`Executable ${executablePath} does not exist`);
17+
return;
18+
}
19+
20+
var options = [];
21+
22+
if (commandArguments) {
23+
options = commandArguments
24+
.replaceAll("\n", " ")
25+
.split(" ")
26+
.map((option) => option.trim())
27+
.filter((option) => option !== " ");
28+
}
29+
30+
options = [...options, ...defaultOptions].filter((option) => option !== "");
31+
32+
return new Process(
33+
executablePath,
34+
{
35+
args: ["format", ...Array.from(new Set(options))],
36+
stdio: "pipe",
37+
cwd: nova.workspace.path, // NOTE: must be explicitly set
38+
}
39+
);
40+
}
41+
42+
async getPromiseToFormat(editor) {
43+
if (!this.config.get("formatOnSave")) return;
44+
45+
return new Promise((resolve, reject) => {
46+
this.format(editor, resolve, reject);
47+
});
48+
}
49+
50+
async format(editor, resolve=null, reject=null) {
51+
if (editor.document.isEmpty) {
52+
if (reject) reject("empty file");
53+
return;
54+
}
55+
56+
let process = await this.getProcess(
57+
editor.document.path ? nova.path.basename(editor.document.path) : null
58+
);
59+
60+
if (!process) {
61+
if (reject) reject("no process");
62+
return;
63+
}
64+
65+
const textRange = new Range(0, editor.document.length);
66+
const content = editor.document.getTextInRange(textRange);
67+
const filePath = nova.workspace.relativizePath(editor.document.path);
68+
69+
let outBuffer = [];
70+
let errBuffer = [];
71+
72+
process.onStdout((output) => outBuffer.push(output));
73+
process.onStderr((error) => errBuffer.push(error));
74+
process.onDidExit((status) => {
75+
if (status === 0) {
76+
const formattedContent = outBuffer.join("");
77+
78+
let result = editor.edit((edit) => {
79+
if (formattedContent !== content) {
80+
console.log("Formatting " + filePath);
81+
edit.replace(textRange, formattedContent, InsertTextFormat.PlainText);
82+
} else {
83+
console.log("Nothing to format");
84+
}
85+
});
86+
87+
if (resolve) resolve(result);
88+
} else {
89+
console.error(errBuffer.join(""));
90+
if (reject) reject();
91+
}
92+
});
93+
94+
console.log("Running " + process.command + " " + process.args.join(" "));
95+
96+
process.start();
97+
98+
let writer = process.stdin.getWriter();
99+
100+
writer.ready.then(() => {
101+
writer.write(content);
102+
writer.close();
103+
});
104+
}
105+
106+
async formatWorkspace(workspace) {
107+
let process = await this.getProcess(".");
108+
109+
if (!process) {
110+
return;
111+
}
112+
113+
let errBuffer = [];
114+
115+
process.onStderr((error) => errBuffer.push(error));
116+
process.onDidExit((status) => {
117+
if (status === 0) {
118+
console.log("Formatting the workspace");
119+
} else {
120+
console.error(errBuffer.join(""));
121+
}
122+
});
123+
124+
console.log("Running " + process.command + " " + process.args.join(" "));
125+
126+
process.start();
127+
}
128+
}
129+
130+
module.exports = Formatter;

Scripts/main.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
const Config = require("./config");
22
const IssuesProvider = require("./provider");
3+
const Formatter = require("./Formatter");
34

45
exports.activate = function() {
56
const config = new Config();
67
const issueCollection = new IssueCollection("ruff");
78
const issuesProvider = new IssuesProvider(config, issueCollection);
9+
const formatter = new Formatter(config);
810

911
console.info("Executable path: " + config.get("executablePath"));
10-
console.info("Command arguments: " + config.get("commandArguments"));
12+
console.info("Command (check) arguments: " + config.get("commandArguments"));
1113
console.info("Check mode: " + config.get("checkMode"));
14+
console.info("Command (format) arguments: " + config.get("commandFormatArguments"));
15+
console.info("Format on save: " + config.get("formatOnSave"));
1216

1317
var assistant = null;
1418

@@ -34,4 +38,14 @@ exports.activate = function() {
3438
issueCollection.set(editor.document.uri, issues);
3539
});
3640
});
41+
42+
nova.workspace.onDidAddTextEditor((editor) => {
43+
if (editor.document.syntax !== "python") return;
44+
editor.onWillSave(formatter.getPromiseToFormat, formatter);
45+
});
46+
47+
nova.commands.register("formatWithRuff", formatter.format, formatter);
48+
nova.commands.register(
49+
"formatWorkspaceWithRuff", formatter.formatWorkspace, formatter
50+
);
3751
}

extension.json

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"organization": "Aeron",
55
"description": "Ruff, an extremely fast Python linter, written in Rust, for Nova.",
66
"version": "1.0.1",
7-
"categories": ["issues", "commands"],
7+
"categories": ["issues", "commands", "formatters"],
88
"repository": "https://github.com/Aeron/Ruff.novaextension",
99
"bugs": "https://github.com/Aeron/Ruff.novaextension/issues",
1010
"license": "BSD-2-Clause-Patent",
@@ -25,6 +25,21 @@
2525
"filters": {
2626
"syntaxes": ["python"]
2727
}
28+
},
29+
{
30+
"title": "Format with Ruff",
31+
"command": "formatWithRuff",
32+
"shortcut": "cmd-shift-B",
33+
"filters": {
34+
"syntaxes": ["python"]
35+
}
36+
}
37+
],
38+
"extensions": [
39+
{
40+
"title": "Format Workspace with Ruff",
41+
"command": "formatWorkspaceWithRuff",
42+
"shortcut": "cmd-shift-opt-B"
2843
}
2944
]
3045
},
@@ -52,7 +67,7 @@
5267
},
5368
{
5469
"key": "cc.aeron.nova-ruff.commandArguments",
55-
"title": "Command Arguments",
70+
"title": "Command (Check) Arguments",
5671
"description": "Additional arguments. The --output-format and --quiet options are always set.",
5772
"type": "string",
5873
"default": null
@@ -68,6 +83,19 @@
6883
["-", "Command only"]
6984
],
7085
"default": "onChange"
86+
},
87+
{
88+
"key": "cc.aeron.nova-ruff.commandFormatArguments",
89+
"title": "Command (Format) Arguments",
90+
"description": "Additional arguments. The --quiet option is always set. The --stdin-filename is set conditionally.",
91+
"type": "string",
92+
"default": null
93+
},
94+
{
95+
"key": "cc.aeron.nova-ruff.formatOnSave",
96+
"title": "Format on a file save",
97+
"type": "boolean",
98+
"default": true
7199
}
72100
],
73101
"configWorkspace": [
@@ -80,7 +108,7 @@
80108
},
81109
{
82110
"key": "cc.aeron.nova-ruff.commandArguments",
83-
"title": "Command Arguments",
111+
"title": "Command (Check) Arguments",
84112
"description": "Additional arguments. The --output-format and --quiet options are always set.",
85113
"type": "string"
86114
},
@@ -94,6 +122,20 @@
94122
["onSave", "On a file save"],
95123
["-", "Command only"]
96124
]
125+
},
126+
{
127+
"key": "cc.aeron.nova-ruff.commandFormatArguments",
128+
"title": "Command (Format) Arguments",
129+
"description": "Additional arguments. The --quiet option is always set. The --stdin-filename is set conditionally.",
130+
"type": "string",
131+
"default": null
132+
},
133+
{
134+
"key": "cc.aeron.nova-ruff.formatOnSave",
135+
"title": "Format on a file save",
136+
"type": "enum",
137+
"values": [[-1, "Global"], [1, "Yes"], [0, "No"]],
138+
"default": -1
97139
}
98140
]
99141
}

0 commit comments

Comments
 (0)