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

[grammar-finder] Add new Core Package #909

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
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
30 changes: 30 additions & 0 deletions packages/grammar-finder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Grammar-Finder

Discover language grammars for unrecognized files.

## AutoFind

With 'AutoFind' enabled, when Pulsar fails to locate a grammar for the file you've just opened, defaulting to 'Plain Text', `grammar-finder` will automatically contact the Pulsar Package Registry in search of a community package that provides syntax highlighting for the file currently opened.

If any packages are found you can easily view the whole list and install the one that looks best.

When an 'AutoFind' notification appears you can quickly select:
* 'View Available Packages' to view the packages found.
* 'Disable Grammar-Finder for <ext>' to add this extension to the `ignoreExtList`.
* 'Disable AutoFind' to disable 'AutoFind' completely.

## Command Palette

`grammar-finder` adds `grammar-finder:find-grammars-for-file` to the Command Palette, so that at any time you can check if any community packages provide syntax highlighting for the file you are currently working in.

This makes it possible to find grammars for _recognized_ file types — or for unrecognized file types if you’ve disabled `autoFind`.

## Configuration

### `autoFind`

When enabled, `autoFind` will show a notification inviting you to install a suitable grammar for an unrecognized file type.

### `ignoreExtList`

Any file extensions can be added to this list to disable all automatic checks for community packages for those file types. Choosing the “Disable `grammar-finder` for X” option on an `autoFind` notification will automatically add a given file extension to this list. This field should contain a comma-separated list of file extensions without any leading `.`s.
196 changes: 196 additions & 0 deletions packages/grammar-finder/lib/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
const { CompositeDisposable } = require("atom");
const path = require("path");
const PackageListView = require("./package-list-view.js");

class GrammarFinder {
activate() {

// This local variable is intended to act as 'session' storage, or editing
// session storage. Where the next time the editor is opened it's info is gone
this.promptedForExt = [];

atom.grammars.emitter.on("did-auto-assign-grammar", async (data) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relates to my comment: #907 (comment)

Basically, we usually have an API like onDid... to add callbacks. I think it's a good idea to add one to the other PR, and use it here.

In any way - we should never add a callback without a cleanup, so we might want to add a CompositeDisposable here and dispose it when deactivating package

if (!atom.config.get("grammar-finder.autoFind")) {
// autofind is turned off
return;
}

let extOrFalse = this.inspectAssignment(data);
if (!extOrFalse) {
// We got false from inspectAssignment() we don't need to act
return;
}

const ext = extOrFalse.replace(".", "");

const ignoreExtList = atom.config.get("grammar-finder.ignoreExtList");

if (ignoreExtList.includes(ext)) {
// we have been told to ignore this ext
return;
}

if (this.promptedForExt.includes(ext)) {
// If we have already prompted for this extension in this editing session
return;
}

const packages = await this.checkForGrammars(ext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can use await here, right? We need activate to be async, or move this to a different method...


if (packages.length === 0) {
// No packages were found that support this grammar
return;
}

this.promptedForExt.push(ext);

// Lets notify the user about the found packages
this.notify(packages, ext, "Pulsar couldn't identify an installed grammar for this file.");
});

this.disposables = new CompositeDisposable();

this.disposables.add(
atom.commands.add("atom-workspace", {
"grammar-finder:find-grammars-for-file": async () => {
// Here we can let users find a grammar for the current file, even if
// it's already correctly identified
const grammar = atom.workspace.getActiveTextEditor().getGrammar();
const buffer = atom.workspace.getActiveTextEditor().buffer;

let extOrFalse = this.inspectAssignment(
{
grammar: grammar,
buffer: buffer
},
{
ignoreScope: true
}
);

if (!extOrFalse) {
// We didn't find any grammar, since this is manually invoked we may want to alert
atom.notifications.addInfo("Grammar-Finder was unable to identify the file.", { dismissable: true });
return;
}

let ext = extOrFalse.replace(".", "");

const ignoreExtList = atom.config.get("grammar-finder.ignoreExtList");

if (ignoreExtList.includes(ext)) {
// we have been told to ignore this ext, since manually invoked we may want to alert
atom.notifications.addInfo("This file is present on Grammar-Finder's ignore list.", { dismissable: true });
return;
}

const packages = await this.checkForGrammars(ext);

if (packages.length === 0) {
// No packages were found that support this grammar
// since manuall invoked we may want to notify
atom.notifications.addInfo(`Unable to locate any Grammars for '${ext}'.`, { dismissable: true });
return;
}

// Lets notify the user about the found packages
this.notify(packages, ext, `'${packages.length}' Installable Grammars are available for '${ext}'.`);
}
Comment on lines +54 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move all of this to a method, so it's easier to read?

})
);
}

deactivate() {
this.superagent = null;
}

inspectAssignment(data, opts = {}) {
console.log(`grammar-finder.inspectAssignment(${data.grammar.scopeName}, ${data.buffer.getPath()})`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we only log on dev mode? Seems like a "debug info" here.

// data = { grammar, buffer }
// Lets first make sure that the grammar returned is one where no
// grammar could be found for the file.

if (data.grammar.scopeName === "text.plain.null-grammar" || opts.ignoreScope) {
const filePath = data.buffer.getPath();

if (typeof filePath !== "string") {
return false;
}

const parsed = path.parse(filePath);
// NodeJS thinks that if the `.` is the first character of a filename
// then it doesn't count as an extension. But according to our handling
// in Pulsar, the same isn't true.
let ext = false;

if (typeof parsed.ext === "string" && parsed.ext.length > 0) {
ext = parsed.ext;
} else if (typeof parsed.name === "string" && parsed.name.length > 0) {
ext = parsed.name;
}

console.log(`File: ${filePath} - Ext: ${ext}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here about a dev/debug mode


return ext;
} else {
return false;
}
}

async checkForGrammars(ext) {
this.superagent ??= require("superagent");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is superagent?


const res = await fetch(
`https://api.pulsar-edit.dev/api/packages?fileExtension=${ext}`,
{
headers: {
"User-Agent": "Pulsar.Grammar-Finder"
}
}
);

if (res.status !== 200) {
// Return empty array
console.error(`Grammar-Finder received status '${res.status}' from the backend: ${res.body}`);
return [];
}

return res.json();
}

notify(packages, ext, title) {
atom.notifications.addInfo(
title,
{
description: "Would you like to see installable packages that **may** support this file type?",
dismissable: true,
buttons: [
{
text: "View Available Packages",
onDidClick: () => {
let packageListView = new PackageListView(packages);
packageListView.toggle();
}
},
{
text: `Don't suggest packages for '${ext}' files`,
onDidClick: () => {
let ignoreExtList = atom.config.get("grammar-finder.ignoreExtList");
ignoreExtList.push(ext);
atom.config.set("grammar-finder.ignoreExtList", ignoreExtList);
}
},
{
text: "Never suggest packages for unrecognized files",
onDidClick: () => {
atom.config.set("grammar-finder.autoFind", false);
}
}
]
}
);

}
}

module.exports = new GrammarFinder();
Loading
Loading