diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..461c198 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 - First Release +* Every feature added +* Every bug fixed diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ddfd223 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2019 Pablo Gil Fernández + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..64e27e5 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# multiple-find-replace + +Multiple (or batch) Find and Replace using a template file as reference. + +![Multiple find and replace screenshot](multiple-find-replace.gif) + +## Options + +- it works in whole document if nothing is selected or just in selection if there is something selected. + +- splitter sintax can be customized in the package settings. + +- case sensitive or insensitive option for *FIND* command. + +- it also works not only with individual words but also with strings. + +## Usage + +1) The first time it is launched it will create a template file where the *FIND* and *REPLACE* statements should be placed. + +2) Use the splitter sintax (by default it is ' => ') to split the *FIND* and *REPLACE* statements, the FIND one goes on the left and the *REPLACE* one on the right: + +> Find and replace me => Replaced! + +3) Each line will represent a *FIND* and *REPLACE* command, use as many lines as you wish in your template file + +4) Save the template file + +5) Launch the Replace command by keyboard shortcut, menus or contextual menu. + +6) Enjoy! diff --git a/keymaps/multiple-find-replace.json b/keymaps/multiple-find-replace.json new file mode 100644 index 0000000..c85a5f1 --- /dev/null +++ b/keymaps/multiple-find-replace.json @@ -0,0 +1,8 @@ +{ + "atom-workspace": { + "ctrl-alt-cmd-r": "multiple-find-replace:replace", + "ctrl-alt-cmd-R": "multiple-find-replace:replace", + "shift-ctrl-alt-cmd-r": "multiple-find-replace:find", + "shift-ctrl-alt-cmd-R": "multiple-find-replace:find" + } +} diff --git a/lib/config-schema.json b/lib/config-schema.json new file mode 100644 index 0000000..d64cc28 --- /dev/null +++ b/lib/config-schema.json @@ -0,0 +1,30 @@ +{ + "general": { + "type": "object", + "title": "General", + "properties": { + "strippingSintax": { + "type": "string", + "title": "Stripping sintax", + "default": " => ", + "description": "The sintax used to split each line between the FIND and REPLACE statements", + "order": 1 + }, + "matchCase": { + "type": "boolean", + "title": "Match case", + "description": "Case sensitive or disable it for case insensitive.", + "default": true, + "order": 2 + }, + "displayNotifications": { + "type": "boolean", + "title": "Display notifications", + "description": "Display plugin notifications as confirmations as the tasks done.", + "default": true, + "order": 3 + } + }, + "order": 1 + } +} diff --git a/lib/multiple-find-replace.js b/lib/multiple-find-replace.js new file mode 100644 index 0000000..8c2a12a --- /dev/null +++ b/lib/multiple-find-replace.js @@ -0,0 +1,149 @@ +'use babel'; + +import { CompositeDisposable } from 'atom'; +import config from './config-schema.json'; + +// Global Variables +const fs = require("fs"); +var path = atom.packages.getPackageDirPaths('multiple-find-replace').toString() + '/multiple-find-replace/multiple-find-replace-template.txt'; +var finishWithError = false; + + +export default { + + config, + subscriptions: null, + + activate() { + this.subscriptions = new CompositeDisposable() + + this.config = config; + + this.subscriptions.add( + atom.commands.add('atom-workspace', { + 'multiple-find-replace:replace': () => this.replace() + })) + + this.subscriptions.add( + atom.commands.add('atom-workspace', { + 'multiple-find-replace:find': () => this.find() + })) + }, + + deactivate() { + this.subscriptions.dispose() + }, + + find(editor) { + finishWithError = false + // Define defaultText variable after package settings declaration + var defaultText = 'Find and replace me' + atom.config.get('multiple-find-replace.general.strippingSintax') + 'Replaced!'; + fileExists = fs.existsSync(path) + if (fileExists) { + atom.workspace.open(path) + if (atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addSuccess('Multiple Find and Replace', {detail: 'Your last template has been opened'}) + } + } else { + // Asynchronous read + fs.readFile(path, function (err, data) { + if (err) { + fs.writeFile(path, defaultText, function (err) { + if (err) { + if (atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addError('Multiple Find and Replace', {detail: 'The template could not been created'}) + } + } + atom.workspace.open(path) + finishWithError = true + if (finishWithError = true && atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addSuccess('Multiple Find and Replace', {detail: 'A new template has been created and opened'}) + } + }); + } + }); + } + }, + + replace(editor) { + finishWithError = false + if (editor = atom.workspace.getActiveTextEditor()) { + let selection = editor.getSelectedText() + if (selection != '') { + text = editor.getSelectedText(); + editor.insertText(this.doreplacement(text)); + if (finishWithError != true && atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addSuccess('Multiple Find and Replace', {detail: 'Done it only in your selection'}) + } + } else { + text = editor.getText(); + editor.setText(this.doreplacement(text)); + if (finishWithError != true && atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addSuccess('Multiple Find and Replace', {detail: 'Done it in the whole document'}) + } + } + } else { + if (atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addError('Multiple Find and Replace', {detail: 'You are out of the editor!'}) + } + } + console.log("Multiple Find and Replace: program ended"); + }, + + doreplacement(text) { + finishWithError = false + // Define defaultText variable after package settings declaration + var defaultText = 'Find and replace me' + atom.config.get('multiple-find-replace.general.strippingSintax') + 'Replaced!'; + // Asynchronous read + fs.readFile(path, function (err, data) { + if (err) { + fs.writeFile(path, defaultText, function (err) { + if (err) { + if (atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addError('Multiple Find and Replace', {detail: 'The template could not been created'}) + } + } + atom.workspace.open(path) + finishWithError = true + if (finishWithError = true && atom.config.get('multiple-find-replace.general.displayNotifications')) { + atom.notifications.addWarning('Multiple Find and Replace', {detail: 'A new template has been created and opened'}) + } + }); + } + }); + + fileExists = fs.existsSync(path) + if (fileExists) { + // Synchronous read + var data = fs.readFileSync(path); + const fullText = data.toString(); + var lines = '' + lines = fullText.split('\n') + linesQuantity = lines.length - 1 + for (var i = 0; i < linesQuantity; i++) { + if (lines[i].includes(atom.config.get('multiple-find-replace.general.strippingSintax'))) { + line = lines[i].split(atom.config.get('multiple-find-replace.general.strippingSintax')) + line[0] + line[1] + if (atom.config.get('multiple-find-replace.general.matchCase')) { + var findRegExp = new RegExp(line[0], 'g'); + } else { + var findRegExp = new RegExp(line[0], 'gi'); + } + text = text.replace(findRegExp, line[1].toString()); + l = i+1 + } else { + atom.notifications.addError('Multiple Find and Replace', {detail: 'The template has a line without the correct FIND and REPLACE splitter sintax, please check it out.'}) + atom.workspace.open(path) + finishWithError = true + break; + } + } + + } else { + finishWithError = true + } + + return text + } +}; diff --git a/menus/multiple-find-replace.json b/menus/multiple-find-replace.json new file mode 100644 index 0000000..eaef9bd --- /dev/null +++ b/menus/multiple-find-replace.json @@ -0,0 +1,39 @@ +{ + "context-menu": { + "atom-text-editor": [ + { + "label": "Multiple Find and Replace", + "submenu": [ + { + "label": "Find and Replace", + "command": "multiple-find-replace:replace" + }, + { + "label": "Open template", + "command": "multiple-find-replace:find" + } + ] + } + ] + }, + "menu": [ + { + "label": "Packages", + "submenu": [ + { + "label": "Multiple Find and Replace", + "submenu": [ + { + "label": "Find and Replace", + "command": "multiple-find-replace:replace" + }, + { + "label": "Open template", + "command": "multiple-find-replace:find" + } + ] + } + ] + } + ] +} diff --git a/multiple-find-replace-template.txt b/multiple-find-replace-template.txt new file mode 100644 index 0000000..e931436 --- /dev/null +++ b/multiple-find-replace-template.txt @@ -0,0 +1,3 @@ +Find and replace me => Replaced! +hello => bye +xxx => 000 diff --git a/multiple-find-replace.gif b/multiple-find-replace.gif new file mode 100644 index 0000000..a72f9e7 Binary files /dev/null and b/multiple-find-replace.gif differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..08cab73 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "multiple-find-replace", + "main": "./lib/multiple-find-replace", + "version": "1.0.0", + "description": "Multiple find and replace", + "keywords": [ + ], + "activationCommands": { + "atom-workspace": ["multiple-find-replace:replace","multiple-find-replace:find"] + }, + "repository": "https://github.com/pgilfernandez/multiple-find-replace", + "license": "MIT", + "engines": { + "atom": ">=1.0.0 <2.0.0" + }, + "dependencies": { + } +} diff --git a/spec/multiple-find-replace-spec.js b/spec/multiple-find-replace-spec.js new file mode 100644 index 0000000..47d3dee --- /dev/null +++ b/spec/multiple-find-replace-spec.js @@ -0,0 +1,73 @@ +'use babel'; + +import MultipleFindReplace from '../lib/multiple-find-replace'; + +// Use the command ` ` (cmd-alt-ctrl-p) to run specs. +// +// To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` +// or `fdescribe`). Remove the `f` to unfocus the block. + +describe('MultipleFindReplace', () => { + let workspaceElement, activationPromise; + + beforeEach(() => { + workspaceElement = atom.views.getView(atom.workspace); + activationPromise = atom.packages.activatePackage('multiple-find-replace'); + }); + + describe('when the multiple-find-replace:replace event is triggered', () => { + it('hides and shows the modal panel', () => { + // Before the activation event the view is not on the DOM, and no panel + // has been created + expect(workspaceElement.querySelector('.multiple-find-replace')).not.toExist(); + + // This is an activation event, triggering it will cause the package to be + // activated. + atom.commands.dispatch(workspaceElement, 'multiple-find-replace:replace'); + + waitsForPromise(() => { + return activationPromise; + }); + + runs(() => { + expect(workspaceElement.querySelector('.multiple-find-replace')).toExist(); + + let multipleFindReplaceElement = workspaceElement.querySelector('.multiple-find-replace'); + expect(multipleFindReplaceElement).toExist(); + + let multipleFindReplacePanel = atom.workspace.panelForItem(multipleFindReplaceElement); + expect(multipleFindReplacePanel.isVisible()).toBe(true); + atom.commands.dispatch(workspaceElement, 'multiple-find-replace:replace'); + expect(multipleFindReplacePanel.isVisible()).toBe(false); + }); + }); + + it('hides and shows the view', () => { + // This test shows you an integration test testing at the view level. + + // Attaching the workspaceElement to the DOM is required to allow the + // `toBeVisible()` matchers to work. Anything testing visibility or focus + // requires that the workspaceElement is on the DOM. Tests that attach the + // workspaceElement to the DOM are generally slower than those off DOM. + jasmine.attachToDOM(workspaceElement); + + expect(workspaceElement.querySelector('.multiple-find-replace')).not.toExist(); + + // This is an activation event, triggering it causes the package to be + // activated. + atom.commands.dispatch(workspaceElement, 'multiple-find-replace:replace'); + + waitsForPromise(() => { + return activationPromise; + }); + + runs(() => { + // Now we can test for view visibility + let multipleFindReplaceElement = workspaceElement.querySelector('.multiple-find-replace'); + expect(multipleFindReplaceElement).toBeVisible(); + atom.commands.dispatch(workspaceElement, 'multiple-find-replace:replace'); + expect(multipleFindReplaceElement).not.toBeVisible(); + }); + }); + }); +}); diff --git a/styles/multiple-find-replace.less b/styles/multiple-find-replace.less new file mode 100644 index 0000000..0ef98fb --- /dev/null +++ b/styles/multiple-find-replace.less @@ -0,0 +1,8 @@ +// The ui-variables file is provided by base themes provided by Atom. +// +// See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less +// for a full listing of what's available. +@import "ui-variables"; + +.multiple-find-replace { +}