How to Install Packages in Atom

 

Atom is a text editor hackable to its core. It allows you to modify and add functionality to better fit your needs.

Yeah, OK, but what does it mean to be a hackable editor?

Everything in Atom is a package and every feature comes in the form of a package. This makes it a highly modular text editor. It is so modular that anyone can write packages for it.

Atom has a bunch of people contributing to it on github, so don’t hesitate to lend a hand!

How to Install a Package?

There are two ways to install packages for Atom,

  1. Enter apm install package-name on your terminal. Obviously, the Atom package manager, apm, must be installed (you can enter apm to verify installation).
  2. Open Atom, go to edit > preferences > install and search for the package you wish to install.

Both of these methods will download your packages to the default directory (e.g., ~/.atom/packages on linux).

Package States

A package can be active, loaded, unloaded, or inactive. Internally, the PackageManager class (in package-manager.js) manages these states.

Loaded Packages

When a package is loaded it means that Atom knows it is installed and that it will be either activated or deactivated. First, Atom will get every available package by saving the required paths (i.e., folders containing the packages) in an array and use that to create another array containing the packages found on those directories. Loading a package causes Atom to read and parse the package's metadata and resources such as keymaps, menus, stylesheets, etc.

loadPackages () {
// Ensure atom exports is already in the require cache so the load time
// of the first package isn't skewed by being the first to require atom
require('../exports/atom')

const disabledPackageNames = new Set(this.config.get('core.disabledPackages'))
this.config.transact(() => {
for (const pack of this.getAvailablePackages()) {
this.loadAvailablePackage(pack, disabledPackageNames)
}
})
this.initialPackagesLoaded = true
this.emitter.emit('did-load-initial-packages')
}
 getAvailablePackages () {
const packages = []
const packagesByName = new Set()

for (const packageDirPath of this.packageDirPaths) {
if (fs.isDirectorySync(packageDirPath)) {
for (let packagePath of fs.readdirSync(packageDirPath)) {
packagePath = path.join(packageDirPath, packagePath)
const packageName = path.basename(packagePath)
if (!packageName.startsWith('.') && !packagesByName.has(packageName) && fs.isDirectorySync(packagePath)) {
packages.push({
name: packageName,
path: packagePath,
isBundled: false
})
packagesByName.add(packageName)
}
}
}
}

for (const packageName in this.packageDependencies) {
if (!packagesByName.has(packageName)) {
packages.push({
name: packageName,
path: path.join(this.resourcePath, 'node_modules', packageName),
isBundled: true
})
}
}

return packages.sort((a, b) => a.name.localeCompare(b.name))
}

Unloaded Packages

Unloading a package removes it completely from the PackageManager. Here Atom will look for that package in the loadedPackages list and remove it.

unloadPackages () {
_.keys(this.loadedPackages).forEach(name => this.unloadPackage(name))
}

unloadPackage (name) {
if (this.isPackageActive(name)) {
throw new Error(`Tried to unload active package '${name}'`)
}
const pack = this.getLoadedPackage(name)
if (pack) {
delete this.loadedPackages[pack.name]
this.emitter.emit('did-unload-package', pack)
} else {
throw new Error(`No loaded package for name '${name}'`)
}
}

Active Packages

When a package is activated the activate() method on the PackageManager is called. It gets every loaded package and tries to call activate() on the package's main module.

This function skips every package that was disabled by the user.

activatePackages (packages) {
const promises = []
this.config.transactAsync(() => {
for (const pack of packages) {
const promise = this.activatePackage(pack.name)
if (!pack.activationShouldBeDeferred()) {
promises.push(promise)
}
}
return Promise.all(promises)
})
this.observeDisabledPackages()
this.observePackagesWithKeymapsDisabled()
return promises
}

Inactive Packages

Deactivating a package unregisters the package's resources and calls deactivate() on the package's main module.

// Deactivate all packages
async deactivatePackages () {
await this.config.transactAsync(() =>
Promise.all(this.getLoadedPackages().map(pack => this.deactivatePackage(pack.name, true)))
)
this.unobserveDisabledPackages()
this.unobservePackagesWithKeymapsDisabled()
}

// Deactivate the package with the given name
async deactivatePackage (name, suppressSerialization) {
const pack = this.getLoadedPackage(name)
if (pack == null) {
return
}

if (!suppressSerialization && this.isPackageActive(pack.name)) {
this.serializePackage(pack)
}

const deactivationResult = pack.deactivate()
if (deactivationResult && typeof deactivationResult.then === 'function') {
await deactivationResult
}

delete this.activePackages[pack.name]
delete this.activatingPackages[pack.name]
this.emitter.emit('did-deactivate-package', pack)
}

Turns out Atom is constantly keeping an eye on its packages folder, and whenever it sees a change a callback is executed.

The PathWatcher class is defined in path-watcher.js and it is used to manage a subscription to file system events that occur beneath a root directory.

The flow works this way,

  • first you install a package by adding it to the packages folder,
  • the PathWatcher detects the change and returns the according callback,
  • then the PackageManager decides what to do with the new package.

Wish to Contribute?

As I mentioned before, everyone is welcomed to contribute! You can also create your own packages. I will not go into details about it here since they have done an amazing job at that already. Just head to their site and follow their tutorial on How to Hack Atom.

 

Share this post