Table of Contents generated with DocToc
- Building your first Atom plugin
- This repo is the result of following the tutorial Building your first Atom plugin Authored by GitHub Campus Expert @NickTikhonov.
- This repo ULL-ESIT-GRADOII-TFG/sourcefetch-tutorial
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(master)]$ apm -v
apm 2.1.2
npm 6.2.0
node 8.9.3 x64
atom 1.33.0
python 2.7.9
git 2.17.1
Start in an empty folder or go to branch begin
.
If you start with an empty folder you can do this:
- Let’s begin by creating a new package using a utility provided by Atom.
- Launch the editor and press
Cmd+Shift+P
(on MacOS) or Ctrl+Shift+P (on Windows/Linux) to open the Command Palette. - Search for
“Package Generator: Generate Package”
and click the corresponding item on the list. - You will see a prompt where you can enter the name of the package -
“sourcefetch”
. - Press enter to generate the starter package, which should automatically be opened in Atom.
- If you don’t see package files appear in the sidebar, press Cmd+K Cmd+B (on MacOS) or Ctrl+K Ctrl+B (on Windows/Linux).
Otherwise go to the branch begin
: it has the same initial code
They are in ~/.atom/packages
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(step-1)]$ ls -l ~/.atom/packages/sourcefetch
lrwxr-xr-x 1 casiano staff 33 18 dic 10:48 /Users/casiano/.atom/packages/sourcefetch -> /Users/casiano/github/sourcefetch
Observe the symbolic link
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(step-1)]$ ls -la ~/.atom/packages/sourcefetch
lrwxr-xr-x 1 casiano staff 33 18 dic 10:48 /Users/casiano/.atom/packages/sourcefetch -> /Users/casiano/github/sourcefetch
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(step-1)]$ rm ~/.atom/packages/sourcefetch
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(step-1)]$ ln -s ~/TFGsrc/build-atom-plugin-sourcefetch-tutorial/ ~/.atom/packages/sourcefetch
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(step-1)]$ ls -la ~/.atom/packages/sourcefetch
lrwxr-xr-x 1 casiano staff 61 18 dic 12:41 /Users/casiano/.atom/packages/sourcefetch -> /Users/casiano/TFGsrc/build-atom-plugin-sourcefetch-tutorial/
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(master)]$ ls -l
total 40
-rw-r--r-- 1 casiano staff 65 18 dic 10:35 CHANGELOG.md
-rw-r--r-- 1 casiano staff 1060 18 dic 10:35 LICENSE.md
-rw-r--r-- 1 casiano staff 7817 18 dic 12:50 README.md
drwxr-xr-x 3 casiano staff 96 18 dic 12:50 keymaps
drwxr-xr-x 3 casiano staff 96 18 dic 12:50 lib
drwxr-xr-x 3 casiano staff 96 18 dic 12:50 menus
-rw-r--r-- 1 casiano staff 452 18 dic 12:50 package.json
lrwxr-xr-x 1 casiano staff 41 18 dic 12:40 sourcefetch -> /Users/casiano/.atom/packages/sourcefetch
drwxr-xr-x 4 casiano staff 128 18 dic 10:35 spec
drwxr-xr-x 3 casiano staff 96 18 dic 10:35 styles
Or shorter:
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(master)]$ apm help link
Usage: apm link [<package_path>] [--name <package_name>]
Create a symlink for the package in ~/.atom/packages. The package in the
current working directory is linked if no path is given.
Run `apm links` to view all the currently linked packages.
Opciones:
-h, --help Print this usage message
-d, --dev Link to ~/.atom/dev/packages [boolean]
Or use apm develop
:
~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(master)]$ apm help develop
Usage: apm develop <package_name> [<directory>]
Clone the given package's Git repository to the directory specified,
install its dependencies, and link it for development to
~/.atom/dev/packages/<package_name>.
If no directory is specified then the repository is cloned to
~/github/<package_name>. The default folder to clone packages into can
be overridden using the ATOM_REPOS_HOME environment variable.
Once this command completes you can open a dev window from atom using
cmd-shift-o to run the package out of the newly cloned repository.
Opciones:
-h, --help Print this usage message
The workflow - as far as I understand it - is:
apm develop <package_name>
cd ~/.atom/dev/packages/<package_name>
atom -d
- First command clones the github repo into
~/github/<package_name>
and links it to~/.atom/dev/packages/<package_name>
, - second is obvious,
- and third runs atom in development mode in which it’s also loading
development
packages fromdev/packages/
- If you want to use your modified package in normal mode, too, simply link create a link to it in
~/.atom/packages/
- You’ll of course need atom’s shell commands installed for all that.
Observe option -d
, and ATOM_DEV_RESOURCE_PATH
and ATOM_HOME
.
See Load developing package
[~/TFGsrc/build-atom-plugin-sourcefetch-tutorial(master)]$ atom --help
Atom Editor v1.33.0
Usage:
atom [options] [path ...]
atom file[:line[:column]]
One or more paths to files or folders may be specified. If there is an
existing Atom window that contains all of the given folders, the paths
will be opened in that window. Otherwise, they will be opened in a new
window.
A file may be opened at the desired line (and optionally column) by
appending the numbers right after the file name, e.g. `atom file:5:8`.
Paths that start with `atom://` will be interpreted as URLs.
Environment Variables:
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to `~/github/atom`.
ATOM_HOME The root path for all configuration files and folders.
Defaults to `~/.atom`.
Opciones:
-1, --one This option is no longer supported. [boolean]
--include-deprecated-apis This option is not currently supported. [boolean]
-d, --dev Run in development mode. [boolean]
-f, --foreground Keep the main process in the foreground. [boolean]
-h, --help Print this usage message. [boolean]
-l, --log-file Log all output to file. [cadena de caracteres]
-n, --new-window Open a new window. [boolean]
--profile-startup Create a profile of the startup execution time. [boolean]
-r, --resource-path Set the path to the Atom source directory and enable dev-mode. [cadena de caracteres]
--safe Do not load packages from ~/.atom/packages or ~/.atom/dev/packages. [boolean]
--benchmark Open a new window that runs the specified benchmarks. [boolean]
--benchmark-test Run a faster version of the benchmarks in headless mode. [boolean]
-t, --test Run the specified specs and exit with error code on failures. [boolean]
-m, --main-process Run the specified specs in the main process. [boolean]
--timeout When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130). [cadena de caracteres]
-v, --version Print the version information. [boolean]
-w, --wait Wait for window to be closed before returning. [boolean]
--clear-window-state Delete all Atom environment state. [boolean]
--enable-electron-logging Enable low-level logging messages from Electron. [boolean]
-a, --add Open path as a new project in last used window. [boolean]
Let’s try out the starter package before diving into the code itself. We will first need to reload Atom to make it aware of the new package that was added. Open the Command Palette again and run the “Window: Reload”
command.
Reloading the current window ensures that Atom runs the latest version of our source code. We will be running this command every time we want to test the changes we make to our package.
Run the package toggle command by navigating to
Packages > sourcefetch > Toggle
using the editor menu, or run
sourcefetch: Toggle
using the Command Palette.
You should see a black box appear at the top of the screen. Hide it by running the command again.
Let’s open lib/sourcefetch.js
, which contains the package logic and defines the toggle
command.
toggle() {
console.log('Sourcefetch was toggled!');
return (
this.modalPanel.isVisible() ? this.modalPanel.hide() : this.modalPanel.show()
);
}
toggle
is a function exported by the module.
It uses a ternary operator to call show
and hide
on the modal panel based on its visibility.
modalPanel
is an instance of Panel
,
a UI element provided by the Atom API.
A Panel
is a container representing a panel on the edges of the editor window.
You should not create a Panel
directly, instead use Workspace::addTopPanel
and friends to add panels.
We declare modalPanel
inside export
default, which lets us access it as an instance variable with this
.
this.subscriptions.add(atom.commands.add('atom-workspace', {
'sourcefetch:toggle': () => this.toggle()
}));
The above statement tells Atom
to execute toggle
every time the user runs sourcefetch:toggle
.
We subscribe an anonymous function, () => this.toggle()
,
to be called every time the command is run.
This is an example of event-driven programming, a common paradigm in JavaScript.
activate(state) {
this.sourcefetchView = new SourcefetchView(state.sourcefetchViewState);
this.modalPanel = atom.workspace.addModalPanel({
item: this.sourcefetchView.getElement(),
visible: false
});
// Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
this.subscriptions = new CompositeDisposable();
// Register command that toggles this view
this.subscriptions.add(atom.commands.add('atom-workspace', {
'sourcefetch:toggle': () => this.toggle()
}));
},
Commands are nothing more than string identifiers for events triggered by the user, defined within a package namespace. We’ve already used:
package-generator:generate-package
window:reload
sourcefetch:toggle
Packages subscribe
to commands
in order to execute code in response to these events.
Let’s make our first code change—we’re going to change toggle
to reverse text selected by the user.
Change the toggle function to match the snippet below (see
file [lib/sourcefetch.js](https://github.com/ULL-ESIT-GRADOII-TFG/sourcefetch-tutorial/blob/step-1/lib/sourcefetch.js#L40-L47branch step-1
).
toggle() {
let editor
if (editor = atom.workspace.getActiveTextEditor()) {
let selection = editor.getSelectedText()
let reversed = selection.split('').reverse().join('')
editor.insertText(reversed)
}
}
Reload Atom by running Window: Reload
in the Command Palette
Navigate to
File > New
to create a new file, type anything you like and select it with the cursor.
Run the sourcefetch:toggle
command using the Command Palette, Atom menu,
or by right clicking and selecting `“Toggle sourcefetch”``
The updated command will toggle the order of the selected text:
See all code changes for this step in the sourcefetch tutorial repository.
The code we added uses the TextEditor API to access and manipulate the text inside the editor.
The TextEditor class represents all essential editing state for a single TextBuffer, including cursor and selection positions, folds, and soft wraps. If you're manipulating the state of an editor, use this class.
Let’s take a closer look.
let editor
if (editor = atom.workspace.getActiveTextEditor()) { /* ... */ }
The first two lines obtain a reference to a TextEditor instance.
The variable assignment and following code is wrapped in a conditional to handle the case where there is no text editor instance available, for example, if the command was run while the user was in the settings menu.
let selection = editor.getSelectedText()
Calling getSelectedText
gives us access to text selected by the user.
If no text is currently selected, the function returns an empty string.
let reversed = selection.split('').reverse().join('')
editor.insertText(reversed)
Our selected text is reversed using JavaScript String methods.
Finally, we call insertText
to replace the selected text with the reversed counterpart.
You can learn more about the different TextEditor methods available by reading the Atom API documentation.
Now that we’ve made our first code change, let’s take a closer look at how an Atom package is organized by exploring the starter code.
The main file is the entry-point to an Atom package.
Atom knows where to find the main file from an entry in package.json
:
"main": "./lib/sourcefetch",
The file exports an object with lifecycle functions which Atom calls on certain events:
- activate is called when the package is initially loaded by Atom.
- This function is used to initialize objects such as user interface elements needed by the package, and to subscribe handler functions to package commands.
- deactivate is called when the package is deactivated, for example, when the editor is closed or refreshed by the user.
- serialize is called by Atom to allow you to save the state of the package between uses. i
- The returned value is passed as an argument to
activate
when the package is next loaded by Atom.
- The returned value is passed as an argument to
We are going to rename our package command to fetch
, and remove user interface elements we won’t be using.
Update the file to match the version below (see it on branch step-2 or git co step-2
):
'use babel';
import { CompositeDisposable } from 'atom'
export default {
subscriptions: null,
activate() {
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.commands.add('atom-workspace', {
'sourcefetch:fetch': () => this.fetch()
}))
},
deactivate() {
this.subscriptions.dispose()
},
fetch() {
let editor
if (editor = atom.workspace.getActiveTextEditor()) {
let selection = editor.getSelectedText()
selection = selection.split('').reverse().join('')
editor.insertText(selection)
}
}
};
To improve performance, Atom packages can be lazy loading.
We can tell Atom to load our package only when certain commands are run by the user.
These commands are called activation commands and are defined in package.json
:
"activationCommands": {
"atom-workspace": "sourcefetch:toggle"
},
Update this entry to make fetch an activation command.
"activationCommands": {
"atom-workspace": "sourcefetch:fetch"
},
Some packages, such as those which modify Atom’s appearance need to be loaded on startup. In those cases, activationCommands
can be omitted entirely.
JSON files inside the menus folder specify which menu items are created for our package. Let’s take a look at menus/sourcefetch.json:
"context-menu": {
"atom-text-editor": [
{
"label": "Toggle sourcefetch",
"command": "sourcefetch:toggle"
}
]
},
The context-menu
object lets us define new items in the right-click
menu.
- Each item is defined by a
label
to be displayed in the menu - and a
command
to run when the item is clicked.
"context-menu": {
"atom-text-editor": [
{
"label": "Fetch code",
"command": "sourcefetch:fetch"
}
]
},
The menu object in the same file defines custom application menu items created for the package.
We’re going to rename this entry as well:
"menu": [
{
"label": "Packages",
"submenu": [
{
"label": "sourcefetch",
"submenu": [
{
"label": "Fetch code",
"command": "sourcefetch:fetch"
}
]
}
]
}
]
Commands can also be triggered with keyboard shortcuts, defined with JSON files in the keymaps directory:
{
"atom-workspace": {
"ctrl-alt-o": "sourcefetch:toggle"
}
}
The above lets package users call toggle with Ctrl+Alt+O
on Windows/Linux or Cmd+Alt+O
on MacOS.
Rename the referenced command to fetch
:
"ctrl-alt-o": "sourcefetch:fetch"
If you have made the changes, reload Atom by running the Window: Reload
command.
Or alternatively, go to branch step-2
and check the solution.
You should see that the application and right-click
menus are updated,
and the reverse functionality should work as before.
See all code changes for this step in the sourcefetch tutorial repository.
Now that we’ve made our first code change and learned about Atom package structure, let’s introduce our first dependency—a module from Node Package Manager (npm).
We will use the request module to make HTTP requests and download the HTML of a website.
This functionality will be needed later, to scrape StackOverflow pages.
Open your command line application, navigate to your package root directory and run:
npm install --save [email protected]
apm install
These commands add the request Node module to our dependencies list and install the module into the node_modules
directory.
You should see a new entry in package.json
.
The @
symbol tells npm to install the specific version we will be using for this tutorial.
Running apm
install lets Atom know to use our newly installed module.
"dependencies": {
"request": "^2.73.0"
}