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

Add format on save option #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"bracketSpacing": true,
}
11 changes: 10 additions & 1 deletion RuboCop.novaextension/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,14 @@
],
"entitlements": {
"process": true
}
},
"config-workspace": [
{
"key": "rubocop.format-on-save",
"title": "Format on save",
"description": "Whether to format documents when saved.",
"type": "boolean",
"default": true
}
]
}
38 changes: 38 additions & 0 deletions Source/Scripts/Formatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// RuboCop Extension for Nova
// Linter.js
//
// Copyright © 2019-2020 Justin Mecham. All rights reserved.
//

const RuboCopProcess = require("./RuboCopProcess");

class Formatter {
constructor() {
this.issues = new IssueCollection();
this.process = new RuboCopProcess();
}

async formatDocument(document) {
if (document.syntax !== "ruby") return;

const contentRange = new Range(0, document.length);
const content = document.getTextInRange(contentRange);

return this.formatString(content, document.path);
}

async formatString(string, uri) {
// this.process.onComplete(offenses => {
// this.issues.set(uri, offenses.map(offense => offense.issue));
// });
//
this.process.execute(string, uri, true);
}

removeIssues(uri) {
this.issues.remove(uri);
}
}

module.exports = Formatter;
9 changes: 5 additions & 4 deletions Source/Scripts/Linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
const RuboCopProcess = require("./RuboCopProcess");

class Linter {

constructor() {
this.issues = new IssueCollection();
this.process = new RuboCopProcess();
Expand All @@ -24,8 +23,11 @@ class Linter {
}

async lintString(string, uri) {
this.process.onComplete(offenses => {
this.issues.set(uri, offenses.map(offense => offense.issue));
this.process.onComplete((offenses) => {
this.issues.set(
uri,
offenses.map((offense) => offense.issue)
);
});

this.process.execute(string, uri);
Expand All @@ -34,7 +36,6 @@ class Linter {
removeIssues(uri) {
this.issues.remove(uri);
}

}

module.exports = Linter;
100 changes: 57 additions & 43 deletions Source/Scripts/RuboCopProcess.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
const Offense = require("./Offense");

class RuboCopProcess {

constructor() {
this.isNotified = false;
}
Expand All @@ -18,26 +17,31 @@ class RuboCopProcess {
return Promise.resolve(this._isBundled);
}

if (!(nova.workspace.contains("Gemfile") || nova.workspace.contains("gems.rb"))) {
if (
!(
nova.workspace.contains("Gemfile") ||
nova.workspace.contains("gems.rb")
)
) {
this._isBundled = false;
return Promise.resolve(false);
}

return new Promise(resolve => {
return new Promise((resolve) => {
const process = new Process("/usr/bin/env", {
args: ["bundle", "exec", "rubocop", "--version"],
cwd: nova.workspace.path,
shell: true
shell: true,
});

let output = "";
process.onStdout(line => output += line.trim());
process.onDidExit(status => {
process.onStdout((line) => (output += line.trim()));
process.onDidExit((status) => {
if (status === 0) {
console.log(`Found RuboCop ${output} (Bundled)`);
resolve(this._isBundled = true);
resolve((this._isBundled = true));
} else {
resolve(this._isBundled = false);
resolve((this._isBundled = false));
}
});

Expand All @@ -50,21 +54,21 @@ class RuboCopProcess {
return Promise.resolve(this._isGlobal);
}

return new Promise(resolve => {
return new Promise((resolve) => {
const process = new Process("/usr/bin/env", {
args: ["rubocop", "--version"],
cwd: nova.workspace.path,
shell: true
shell: true,
});

let output = "";
process.onStdout(line => output += line.trim());
process.onDidExit(status => {
process.onStdout((line) => (output += line.trim()));
process.onDidExit((status) => {
if (status === 0) {
console.log(`Found RuboCop ${output} (Global)`);
resolve(this._isGlobal = true);
resolve((this._isGlobal = true));
} else {
resolve(this._isGlobal = false);
resolve((this._isGlobal = false));
}
});

Expand All @@ -84,38 +88,41 @@ class RuboCopProcess {
args: commandArguments,
cwd: nova.workspace.path,
shell: true,
stdio: "pipe"
stdio: "pipe",
});

return process;
}

async execute(content, uri) {
async execute(content, uri, format = false) {
let workspaceOverlap = uri.indexOf(nova.workspace.path);
let relativePath = '';

if (workspaceOverlap != -1) {
relativePath = uri.substring(workspaceOverlap + nova.workspace.path.length + 1);
} else {
relativePath = uri;
}

const defaultArguments = [
"rubocop",
"--format=json",
"--stdin",
relativePath
];

const defaultArguments = ["rubocop", "--format=json"];
if (format) {
defaultArguments.push("--auto-correct");
} else {
defaultArguments.push("--stdin");
}
defaultArguments.push(relativePath);

const process = await this.process(defaultArguments);
if (!process) return;

let output = "";
let errorOutput = "";
process.onStdout(line => output += line);
process.onStderr(line => errorOutput += line);
process.onDidExit(status => {
process.onStdout((line) => (output += line));
process.onStderr((line) => (errorOutput += line));
process.onDidExit((status) => {
// See: https://github.com/rubocop-hq/rubocop/blob/master/manual/basic_usage.md#exit-codes
status >= 2 ? this.handleError(errorOutput) : this.handleOutput(output, errorOutput);
status >= 2
? this.handleError(errorOutput)
: this.handleOutput(output, errorOutput);
});

process.start();
Expand Down Expand Up @@ -146,12 +153,14 @@ class RuboCopProcess {
try {
const parsedOutput = JSON.parse(output);
const offenses = parsedOutput["files"][0]["offenses"];


if (offenses) {
this.offenses = offenses.map((offense) => new Offense(offense));
}

// TODO: Enable a "Debug" Preference
// console.info(JSON.stringify(offenses, null, " "));

this.offenses = offenses.map(offense => new Offense(offense));
} catch(error) {
} catch (error) {
console.error(error);
}

Expand All @@ -165,19 +174,25 @@ class RuboCopProcess {

const request = new NotificationRequest("rubocop-not-found");
request.title = nova.localize("RuboCop Not Found");
request.body = nova.localize("The \"rubocop\" command could not be found in your environment.");
request.body = nova.localize(
'The "rubocop" command could not be found in your environment.'
);
request.actions = [nova.localize("OK"), nova.localize("Help")];

const notificationPromise = nova.notifications.add(request);
notificationPromise.then((response) => {
if (response.actionIdx === 1) { // Help
nova.openConfig();
}
}).catch((error) => {
console.error(error);
}).finally(() => {
this.isNotified = true;
});
notificationPromise
.then((response) => {
if (response.actionIdx === 1) {
// Help
nova.openConfig();
}
})
.catch((error) => {
console.error(error);
})
.finally(() => {
this.isNotified = true;
});
}

notifyUserOfError(errorMessage) {
Expand All @@ -195,7 +210,6 @@ class RuboCopProcess {
onComplete(callback) {
this._onCompleteCallback = callback;
}

}

module.exports = RuboCopProcess;
35 changes: 28 additions & 7 deletions Source/Scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,40 @@
//

const Linter = require("./Linter");
const Formatter = require("./Formatter");

exports.activate = function() {
exports.activate = function () {
const linter = new Linter();

const formatter = new Formatter();
let format = false;

nova.workspace.config.observe(
'rubocop.format-on-save',
() => format = nova.workspace.config.get('rubocop.format-on-save')

)


nova.workspace.onDidAddTextEditor((editor) => {
linter.lintDocument(editor.document);

editor.onWillSave(editor => linter.lintDocument(editor.document));
editor.onDidStopChanging(editor => linter.lintDocument(editor.document));
editor.document.onDidChangeSyntax(document => linter.lintDocument(document));
editor.onWillSave((editor) => {
linter.lintDocument(editor.document)
if (format) {
formatter.formatDocument(editor.document)
}
});

editor.onDidStopChanging((editor) =>
linter.lintDocument(editor.document)
);

editor.document.onDidChangeSyntax((document) =>
linter.lintDocument(document)
);

editor.onDidDestroy(destroyedEditor => {
let anotherEditor = nova.workspace.textEditors.find(editor => {
editor.onDidDestroy((destroyedEditor) => {
let anotherEditor = nova.workspace.textEditors.find((editor) => {
return editor.document.uri === destroyedEditor.document.uri;
});

Expand Down