Repository: prettier/prettier-atom
Pull request #142 (2017-04-25 22:50:56)

Toggle command for "Format on Save" option
Fixes https://github.com/prettier/prettier-atom/issues/117.

The new command is defined as `prettier:toggle-format-on-save`.

I also wanted to add a visual indicator for the command, because it's not practical when you don't know if the option is already enabled or not.

I tried to make something in the status bar with Prettier's logo, but it looked terrible with small dimensions and it didn't fit with the other tiles.
I ended up just adding a "Prettier" label:

<details>

**Enabled**
![](https://i.imgur.com/XCCpwZf.png)

**Disabled**
![](https://i.imgur.com/SwQ4m4l.png)

</details>


Patch:
diff --git a/.eslintrc.yml b/.eslintrc.yml
index e7d57c8..3a19413 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -11,6 +11,7 @@ plugins:
 
 globals:
   atom: true
+  document: false
 
 env:
   node: true
diff --git a/decls/index.js b/decls/index.js
index 097ab6f..d18fe4a 100644
--- a/decls/index.js
+++ b/decls/index.js
@@ -32,6 +32,7 @@ declare type TextEditor = {
     }) => void,
   ) => void,
 };
+declare type Atom$Disposable = any;
 declare type Atom$View = any;
 declare type Atom$Workspace = any;
 declare type Atom$Command = { name: string, displayName: string };
@@ -42,9 +43,14 @@ declare var atom: {
   },
   config: {
     get: (key: string) => any,
+    set: (key: string) => any,
   },
   notifications: {
     addError: (message: string, options?: { detail?: string, dismissable?: boolean }) => void,
+    addInfo: (message: string, options?: { detail?: string, dismissable?: boolean }) => void,
+  },
+  tooltips: {
+    add: (target: HTMLElement, options?: { title?: string }) => Atom$Disposable
   },
   views: {
     getView: Atom$Workspace => Atom$View,
diff --git a/dist/helpers.js b/dist/helpers.js
index dd72ca4..24615cf 100644
--- a/dist/helpers.js
+++ b/dist/helpers.js
@@ -106,6 +106,10 @@ var getConfigOption = function getConfigOption(key) {
   return atom.config.get('prettier-atom.' + key);
 };
 
+var setConfigOption = function setConfigOption(key, value) {
+  return atom.config.set('prettier-atom.' + key, value);
+};
+
 var shouldDisplayErrors = function shouldDisplayErrors() {
   return !getConfigOption('silenceErrors');
 };
@@ -202,6 +206,7 @@ var getDebugInfo = function getDebugInfo() {
 
 module.exports = {
   getConfigOption: getConfigOption,
+  setConfigOption: setConfigOption,
   shouldDisplayErrors: shouldDisplayErrors,
   getPrettierOption: getPrettierOption,
   getPrettierEslintOption: getPrettierEslintOption,
diff --git a/dist/main.js b/dist/main.js
index 74f3f3d..15a57ce 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -6,6 +6,11 @@ var config = require('./config-schema.json');
 var _require = require('atom'),
     CompositeDisposable = _require.CompositeDisposable;
 
+var _require2 = require('./statusTile'),
+    createStatusTile = _require2.createStatusTile,
+    updateStatusTile = _require2.updateStatusTile,
+    disposeTooltip = _require2.disposeTooltip;
+
 // local helpers
 
 
@@ -13,7 +18,10 @@ var format = null;
 var formatOnSave = null;
 var warnAboutLinterEslintFixOnSave = null;
 var displayDebugInfo = null;
+var toggleFormatOnSave = null;
 var subscriptions = null;
+var statusBarTile = null;
+var tileElement = null;
 
 // HACK: lazy load most of the code we need for performance
 var lazyFormat = function lazyFormat() {
@@ -49,12 +57,22 @@ var lazyDisplayDebugInfo = function lazyDisplayDebugInfo() {
   displayDebugInfo();
 };
 
+var lazyToggleFormatOnSave = function lazyToggleFormatOnSave() {
+  if (!toggleFormatOnSave) {
+    // eslint-disable-next-line global-require
+    toggleFormatOnSave = require('./toggleFormatOnSave');
+  }
+  toggleFormatOnSave();
+};
+
 // public API
 var activate = function activate() {
   subscriptions = new CompositeDisposable();
 
   subscriptions.add(atom.commands.add('atom-workspace', 'prettier:format', lazyFormat));
   subscriptions.add(atom.commands.add('atom-workspace', 'prettier:debug', lazyDisplayDebugInfo));
+  subscriptions.add(atom.commands.add('atom-workspace', 'prettier:toggle-format-on-save', lazyToggleFormatOnSave));
+
   subscriptions.add(atom.workspace.observeTextEditors(function (editor) {
     return subscriptions.add(editor.getBuffer().onWillSave(function () {
       return lazyFormatOnSave(editor);
@@ -75,11 +93,29 @@ var activate = function activate() {
 
 var deactivate = function deactivate() {
   subscriptions.dispose();
+  disposeTooltip();
+  if (statusBarTile) {
+    statusBarTile.destroy();
+  }
+};
+
+var consumeStatusBar = function consumeStatusBar(statusBar) {
+  tileElement = createStatusTile();
+  statusBarTile = statusBar.addLeftTile({
+    item: tileElement,
+    priority: 1000
+  });
+  updateStatusTile(subscriptions, tileElement);
+
+  subscriptions.add(atom.config.observe('prettier-atom.formatOnSaveOptions.enabled', function () {
+    return updateStatusTile(subscriptions, tileElement);
+  }));
 };
 
 module.exports = {
   activate: activate,
   deactivate: deactivate,
   config: config,
-  subscriptions: subscriptions
+  subscriptions: subscriptions,
+  consumeStatusBar: consumeStatusBar
 };
\ No newline at end of file
diff --git a/dist/statusTile.js b/dist/statusTile.js
new file mode 100644
index 0000000..b69171e
--- /dev/null
+++ b/dist/statusTile.js
@@ -0,0 +1,45 @@
+'use strict';
+
+var _require = require('./helpers'),
+    getConfigOption = _require.getConfigOption;
+
+var tooltip = null;
+
+var getFormatOnSaveStatus = function getFormatOnSaveStatus() {
+  return getConfigOption('formatOnSaveOptions.enabled') ? 'enabled' : 'disabled';
+};
+
+var createStatusTile = function createStatusTile() {
+  var element = document.createElement('div');
+  element.classList.add('prettier-atom-status-tile');
+  element.classList.add('inline-block');
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+
+  element.appendChild(document.createTextNode('Prettier'));
+
+  return element;
+};
+
+var disposeTooltip = function disposeTooltip() {
+  if (tooltip) {
+    tooltip.dispose();
+  }
+};
+
+var updateStatusTile = function updateStatusTile(disposable, element) {
+  // eslint-disable-next-line no-param-reassign
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+
+  disposeTooltip();
+
+  tooltip = atom.tooltips.add(element, { title: 'Format on Save: ' + getFormatOnSaveStatus() });
+  disposable.add(tooltip);
+
+  return tooltip;
+};
+
+module.exports = {
+  createStatusTile: createStatusTile,
+  updateStatusTile: updateStatusTile,
+  disposeTooltip: disposeTooltip
+};
\ No newline at end of file
diff --git a/dist/toggleFormatOnSave.js b/dist/toggleFormatOnSave.js
new file mode 100644
index 0000000..048ff1e
--- /dev/null
+++ b/dist/toggleFormatOnSave.js
@@ -0,0 +1,12 @@
+'use strict';
+
+var _require = require('./helpers'),
+    getConfigOption = _require.getConfigOption,
+    setConfigOption = _require.setConfigOption;
+
+var toggleFormatOnSave = function toggleFormatOnSave() {
+  var key = 'formatOnSaveOptions.enabled';
+  setConfigOption(key, !getConfigOption(key));
+};
+
+module.exports = toggleFormatOnSave;
\ No newline at end of file
diff --git a/menus/prettier-js.json b/menus/prettier-js.json
index b7eb6cb..0ef587c 100644
--- a/menus/prettier-js.json
+++ b/menus/prettier-js.json
@@ -21,6 +21,10 @@
             {
               "label": "Debug",
               "command": "prettier:debug"
+            },
+            {
+              "label": "Toggle Format on Save",
+              "command": "prettier:toggle-format-on-save"
             }
           ]
         }
diff --git a/package.json b/package.json
index c09ef24..aa70523 100644
--- a/package.json
+++ b/package.json
@@ -77,5 +77,12 @@
       "src"
     ],
     "testEnvironment": "node"
+  },
+  "consumedServices": {
+    "status-bar": {
+      "versions": {
+        "^1.0.0": "consumeStatusBar"
+      }
+    }
   }
 }
diff --git a/src/displayDebugInfo.js b/src/displayDebugInfo.js
index 24ca7fb..7daa03e 100644
--- a/src/displayDebugInfo.js
+++ b/src/displayDebugInfo.js
@@ -1,3 +1,4 @@
+// @flow
 const { getDebugInfo } = require('./helpers');
 
 const displayDebugInfo = () => {
diff --git a/src/helpers.js b/src/helpers.js
index b64e33d..4b04bb2 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -82,6 +82,8 @@ const getDepPath = (dep: string) => path.join(__dirname, '../node_modules', dep)
 // public helpers
 const getConfigOption = (key: string) => atom.config.get(`prettier-atom.${key}`);
 
+const setConfigOption = (key: string, value: any) => atom.config.set(`prettier-atom.${key}`, value);
+
 const shouldDisplayErrors = () => !getConfigOption('silenceErrors');
 
 const getPrettierOption = (key: string) => getConfigOption(`prettierOptions.${key}`);
@@ -153,6 +155,7 @@ const getDebugInfo = () => ({
 
 module.exports = {
   getConfigOption,
+  setConfigOption,
   shouldDisplayErrors,
   getPrettierOption,
   getPrettierEslintOption,
diff --git a/src/main.js b/src/main.js
index 5c91ae7..ac05a14 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,13 +1,21 @@
 const config = require('./config-schema.json');
 // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
 const { CompositeDisposable } = require('atom');
+const {
+  createStatusTile,
+  updateStatusTile,
+  disposeTooltip,
+} = require('./statusTile');
 
 // local helpers
 let format = null;
 let formatOnSave = null;
 let warnAboutLinterEslintFixOnSave = null;
 let displayDebugInfo = null;
+let toggleFormatOnSave = null;
 let subscriptions = null;
+let statusBarTile = null;
+let tileElement = null;
 
 // HACK: lazy load most of the code we need for performance
 const lazyFormat = () => {
@@ -43,12 +51,24 @@ const lazyDisplayDebugInfo = () => {
   displayDebugInfo();
 };
 
+const lazyToggleFormatOnSave = () => {
+  if (!toggleFormatOnSave) {
+    // eslint-disable-next-line global-require
+    toggleFormatOnSave = require('./toggleFormatOnSave');
+  }
+  toggleFormatOnSave();
+};
+
 // public API
 const activate = () => {
   subscriptions = new CompositeDisposable();
 
   subscriptions.add(atom.commands.add('atom-workspace', 'prettier:format', lazyFormat));
   subscriptions.add(atom.commands.add('atom-workspace', 'prettier:debug', lazyDisplayDebugInfo));
+  subscriptions.add(
+    atom.commands.add('atom-workspace', 'prettier:toggle-format-on-save', lazyToggleFormatOnSave),
+  );
+
   subscriptions.add(
     atom.workspace.observeTextEditors(editor =>
       subscriptions.add(editor.getBuffer().onWillSave(() => lazyFormatOnSave(editor))),
@@ -69,6 +89,26 @@ const activate = () => {
 
 const deactivate = () => {
   subscriptions.dispose();
+  disposeTooltip();
+  if (statusBarTile) {
+    statusBarTile.destroy();
+  }
+};
+
+const consumeStatusBar = (statusBar) => {
+  tileElement = createStatusTile();
+  statusBarTile = statusBar.addLeftTile({
+    item: tileElement,
+    priority: 1000,
+  });
+  updateStatusTile(subscriptions, tileElement);
+
+  subscriptions.add(
+    atom.config.observe(
+      'prettier-atom.formatOnSaveOptions.enabled',
+      () => updateStatusTile(subscriptions, tileElement),
+    ),
+  );
 };
 
 module.exports = {
@@ -76,4 +116,5 @@ module.exports = {
   deactivate,
   config,
   subscriptions,
+  consumeStatusBar,
 };
diff --git a/src/statusTile.js b/src/statusTile.js
new file mode 100644
index 0000000..91a1ce6
--- /dev/null
+++ b/src/statusTile.js
@@ -0,0 +1,41 @@
+// @flow
+const { getConfigOption } = require('./helpers');
+
+let tooltip: Atom$Disposable = null;
+
+const getFormatOnSaveStatus = () => getConfigOption('formatOnSaveOptions.enabled') ? 'enabled' : 'disabled';
+
+const createStatusTile = () => {
+  const element = document.createElement('div');
+  element.classList.add('prettier-atom-status-tile');
+  element.classList.add('inline-block');
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+
+  element.appendChild(document.createTextNode('Prettier'));
+
+  return element;
+};
+
+const disposeTooltip = () => {
+  if (tooltip) {
+    tooltip.dispose();
+  }
+};
+
+const updateStatusTile = (disposable: Atom$Disposable, element: HTMLElement) => {
+  // eslint-disable-next-line no-param-reassign
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+
+  disposeTooltip();
+
+  tooltip = atom.tooltips.add(element, { title: `Format on Save: ${getFormatOnSaveStatus()}` });
+  disposable.add(tooltip);
+
+  return tooltip;
+};
+
+module.exports = {
+  createStatusTile,
+  updateStatusTile,
+  disposeTooltip,
+};
diff --git a/src/toggleFormatOnSave.js b/src/toggleFormatOnSave.js
new file mode 100644
index 0000000..d87112d
--- /dev/null
+++ b/src/toggleFormatOnSave.js
@@ -0,0 +1,10 @@
+// @flow
+const { getConfigOption, setConfigOption } = require('./helpers');
+
+const FORMAT_ON_SAVE = 'formatOnSaveOptions.enabled';
+
+const toggleFormatOnSave = () => {
+  setConfigOption(FORMAT_ON_SAVE, !getConfigOption(FORMAT_ON_SAVE));
+};
+
+module.exports = toggleFormatOnSave;
diff --git a/styles/statusTile.css b/styles/statusTile.css
new file mode 100644
index 0000000..9963e12
--- /dev/null
+++ b/styles/statusTile.css
@@ -0,0 +1,7 @@
+div[data-format-on-save="enabled"] {
+  color: #8BC34A;
+}
+
+div[data-format-on-save="enabled"]::after {
+  content: " ✓";
+}


Test patch:
diff --git a/src/statusTile.test.js b/src/statusTile.test.js
new file mode 100644
index 0000000..9dfeaa3
--- /dev/null
+++ b/src/statusTile.test.js
@@ -0,0 +1,48 @@
+// @flow
+const {
+  createStatusTile,
+  updateStatusTile,
+} = require('./statusTile');
+
+beforeEach(() => {
+  atom = {
+    config: { get: jest.fn(() => true) },
+    tooltips: { add: jest.fn(() => ({ dispose: jest.fn() })) },
+  };
+});
+
+describe('createStatusTile()', () => {
+  it('creates a div with "Prettier" and a tooltip indicating formatOnSave status', () => {
+    global.document = {
+      createElement: jest.fn(() => ({
+        classList: {
+          add: jest.fn(),
+        },
+        dataset: {},
+        appendChild: jest.fn(),
+      })),
+      createTextNode: jest.fn(arg => arg),
+    };
+
+    const div = createStatusTile();
+
+    expect(div.dataset.formatOnSave).toBe('enabled');
+    expect(div.classList.add).toHaveBeenCalledWith('prettier-atom-status-tile');
+    expect(div.appendChild).toHaveBeenCalledWith('Prettier');
+  });
+});
+
+describe('updateStatusTile()', () => {
+  it('disposes any existing tooltip and adds a new one with the current formatOnSave status', () => {
+    const div = { dataset: {} };
+    const disposable = { add: jest.fn() };
+
+    // $FlowFixMe
+    const tooltip = updateStatusTile(disposable, div);
+    expect(atom.tooltips.add).toHaveBeenCalledWith(div, { title: 'Format on Save: enabled' });
+
+    // $FlowFixMe
+    updateStatusTile(disposable, div);
+    expect(tooltip.dispose).toHaveBeenCalled();
+  });
+});
diff --git a/src/toggleFormatOnSave.test.js b/src/toggleFormatOnSave.test.js
new file mode 100644
index 0000000..3c486da
--- /dev/null
+++ b/src/toggleFormatOnSave.test.js
@@ -0,0 +1,28 @@
+// @flow
+const toggleFormatOnSave = require('./toggleFormatOnSave');
+
+test('it sets formatOnSaveOptions.enabled to false if it was true', () => {
+  atom = {
+    config: {
+      get: jest.fn(() => true),
+      set: jest.fn(),
+    },
+  };
+
+  toggleFormatOnSave();
+
+  expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', false);
+});
+
+test('it sets formatOnSaveOptions.enabled to true if it was false', () => {
+  atom = {
+    config: {
+      get: jest.fn(() => false),
+      set: jest.fn(),
+    },
+  };
+
+  toggleFormatOnSave();
+
+  expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', true);
+});