Atomの設定・パッケージを複数端末で共有したいメモ


## はじめに

複数端末でAtomの設定やパッケージを共有するためにはどのような方法があるかを模索している。
まだちゃんとしたやり方は定まっていないが、頭の中を整理するためにもメモを残す。

## 方法1: `.atom`ディレクトリをまるごと移す

調べていて多く見つかったのは、DropBoxに`.atom`ディレクトリを移し、各端末でそのディレクトリにリンクを貼るという方法だった。
一番わずらわしくない方法だが、デメリットが許容できないので見送った。

### デメリット

- 各パッケージのファイルまで同期してしまう
  - DropBoxならばまだ許容範囲内かもしれないが、どうせなら`git`でバージョン管理したい
      - 独自パッケージならまだしも、他の方が作成しているパッケージまで`git`管理下に置きたくない
- パッケージによっては`config.cson`にパスが記載されることとなるが、WindowsとLinux/Macでパスの表記方法が異なるため問題が発生すると思われる

## 方法2: sync-settingsパッケージを利用する

コメント欄にて[sync-settings](https://atom.io/packages/sync-settings)というパッケージを紹介いただいた。
各種設定をgistにアップロードして同期を行うというもののようだが、いくつかこちらの要件に合わなかったため申し訳ないが見送ることとした。

### 懸念1: 端末固有としたい設定も同期する

`config.cson`(sync-settingsでは`settings.json`という名称で保存している模様)にはディレクトリパスが記載されるものがある。
例えば`core.projectHome`がそれに当たるのだが、sync-settingsはこれらも同期を行うようだ。
Linux/Mac間での同期であればあまり問題にならないと思うが、ここにWindowsが同期対象として入ってきた場合、ディレクトリパスを同期してしまうと問題が発生することが懸念される。
また、`core.projectHome`以外にもパッケージがパスを追記するケースがあるため、単純に`core.projectHome`を設定しなければ回避できる問題という訳ではない。

### 懸念2: settings-viewのソースを利用している

パッケージのインストールのためにCore Packagesの[settings-view](https://atom.io/packages/settings-view)から[package-manager.coffee](https://github.com/atom/settings-view/blob/master/lib/package-manager.coffee)をコピーして同梱しているようだ。
MITライセンスなので再利用である旨さえ明記しておけばライセンス上は問題ないのだが、Core Packages由来のコードだけあってAPIドキュメントにはないメソッドをいくつも利用している。
非公開のメソッドであるため、特にアナウンスなく挙動が変更となる可能性があるものを数多く利用するのは精神衛生上あまりよろしくない。
（先日非公開のメソッドを利用していたばかりに動作しなくなった[remote-edit](https://atom.io/packages/remote-edit)がよい例である）

### 懸念3: パッケージのアンインストール情報の同期には未対応？

実際に試したわけではないのだが、ソースを読む限り追加のインストールしかしておらず、アンインストールの情報は同期していないようだ。
正確にはパッケージの情報を記載している`packages.json`からは消えているのだろうが、リストアをする際に消えたパッケージをチェックしていないため、とある端末では削除したパッケージが別の端末ではインストールされたままという状態になってしまう。

## 方法3: 個別にがんばる

`.atom`ディレクトリまるごと同期を行わないのであれば、パッケージと各種設定の同期を個別の方法でやる必要がある。
現在は下記の方法を検討している。

### パッケージの同期

#### `apm stars`を使う

パッケージのバージョンまで合わせる必要がない、常に最新バージョンをインストールしたい場合は`apm star`及び`apm stars`で同期が可能となる。

参考: [Atomの apm star が地味に便利](http://qiita.com/st44100/items/e9202cc9c8fb361e1175)

##### インストール済みのパッケージ全てにスターを付ける

```shell-session
$ apm star --installed
```

##### スターを付けたパッケージをインストールする

```shell-session
$ apm stars --install
```

#### `apm list`を使う

パッケージのバージョンまで合わせたい場合、`apm list`でバージョン付きのパッケージ一覧を作成し、`apm install`で一覧を読んでインストールする方法を利用する。

参考: [インストールされてるpackageをgitで管理したい](http://qiita.com/hokaccha/items/52633b76f3ddbb348349)

##### インストール済みパッケージ一覧を作成する

```shell-session
$ apm list --installed --bare > atom-packages.txt
```

##### パッケージ一覧を読んでインストールする

```shell-session
$ apm install --packages-file atom-packages.txt
```

#### 起動時にパッケージをインストールする

*FIXME: あとでもっと調べる*

`init.coffee`で`child_process`を利用して`apm`コマンドを操作すればパッケージのインストールは可能となるはずである。
問題はインストールしたパッケージのロードで、`atom.packages.loadPackages()`を使えばロード自体は出来るのだが、[PackageManager](https://atom.io/docs/api/v1.2.0/PackageManager)のAPIドキュメントを見ても`loadPackages()`の記載がないのでこれを利用するのはあまり褒められたやり方ではないのだろう。
（これを用いると`onDidLoadInitialPackages`イベントが呼ばれるため、パッケージのロード時に一度だけ呼ばれる想定の処理が複数回呼ばれる可能性があり、あまりよろしくない）
`loadPackages()`の処理を模倣するにしても、APIドキュメントに記載のないものをいくつも利用しているため、公開APIだけを利用して模倣するには無理があるようだ。

*2015/11/30 Update 机上の空論追記*

しばらく調べてみたが、APIドキュメントに記載がある範囲ではうまく実現できそうにないと判断した。
もしAPIドキュメントに記載がある範囲で実現する場合、`apm`コマンドで更新した後にパッケージの再ロードのために`Window: Reload`を促すダイアログを表示する必要があるだろう。

sync-settingsパッケージではCore Packagesであるsettings-viewパッケージから`package-manager.coffee`をコピーして利用しているが、コピーしてくるよりは「[他パッケージのモジュールをロードする](http://qiita.com/from_kyushu/items/2082f51416acd0bfab0a)」という別エントリに書いた方法でsettings-viewパッケージ内の`package-manager.coffee`を直接読んだほうがこのファイルのメンテナンスの手間がなくなる分だけまだ手間は少ないと思われる。
`package-manager.coffee`の内部でもAPIドキュメントに記載がないものが多く使われているが、settings-viewはCore Packagesであることと、設定が最新のものに追随しておらずにエラーを吐くような状況で正式リリースされることはないはずなので、`package-manager.coffee`内部で使われている分にはこちらは気にする必要はない。
ただし、どの道この`package-manager.coffee`自体そもそも外部から呼び出すことを想定していないはずなので、ファイルパスが変わったり中身がアナウンスなく書き換わったりする可能性は大いにあるため、このファイルの変更を追って追随する必要がある。

### 各種設定の同期

#### `config.cson`

基本的な設定は同期したいが、端末固有のもの（例えば`core.projectHome`）は個別に設定したい場合、`config.cson`をただ共有する方法を取ることが出来ない。
考えられる手段としては端末固有の設定を除いた`config_global.cson`と、端末固有の設定を記述した`config_local.cson`の2種類のファイルを作成し、同期時にこれらを統合して`config.cson`を生成するという方法。
[local-settings](https://atom.io/packages/local-settings)というプロジェクト固有の設定を読むためのパッケージがあった（が、長らくメンテされておらずIssueを見る限り今は動かないようだが）のでソースを確認したところ、`config.cson`とプロジェクト固有の設定をマージさせて設定を反映させる処理が書いてあったため、これを参考にすれば`init.coffee`内で`config.cson`を生成して再読み込みさせることが出来そうだ。
設定が変更された際のイベントは恐らく[atom.config.onDidChange](https://atom.io/docs/api/v1.2.0/Config#instance-onDidChange)で拾えるはずなので、このイベントを拾って`config_global.cson`と`config_local.cson`に追記してやることも出来るはずである。

あくまで机上の空論であるため、後日時間を取って試すことにする。

*2015/11/30 Update 「実装してみた」を追記*

##### 実装してみた

*2015/12/08 Update 別ウィンドウを開いた際に設定ファイルが壊れるバグが見つかったため削除*

設定ファイルが吹っ飛んでびっくりしたのだが、`config.cson`が空になってしまう不具合があった。
幸い`git`で管理していた分は復旧できたが、危なっかしいので原因をちゃんと調査修正が出来るまでコードは削除しておくことにする。

#### `.apmrc`

これは基本端末固有の設定になるはずなので、そもそも同期の必要がないのではないかと思われる。

#### その他キーマップや起動スクリプト等

基本的に端末固有のものはないので、そのまま`git`で管理するなり`DropBox`に放り込んで各端末でリンクするなりすればよいと思う。

##### 起動スクリプトに端末固有のものを書きたい場合

例えば`.atom`内や`$HOME`(Windowsなら`%USERPROFILE%`が適切？)以下に`.atom_init.coffee`と言った各端末固有のスクリプトを作成し、`init.coffee`内でそれらを`require`する方法が考えられる。

```coffeescript:.atom_init.coffee
 # 各端末固有の起動処理例: 環境変数PATHにパスを追加
 process.env.PATH = ["/path/to/targetdir", process.env.PATH].join(":")
```

```coffeescript:init.coffee
path = require 'path'
fs = require 'fs'

localInitPath = path.join process.env.HOME, ".atom_init.coffee"
require localInitPath if fs.existsSync localInitPath
```