Package 作成時の便利フレーズ、知識など


Atom の Package 開発でよく使うフレーズ、知識等を気まぐれに追記していきます。

自分の理解整理の為の dump が目的です。
API の使い方は、[本体のコード](https://github.com/atom/atom) や、ユーザー [atom](https://atom.io/users/atom) の Package を読むのが一番よい。

ちなみに、ここにまとめる為に整理、変更して書いてるので、自分の Package 内で使っているものとは若干違います。
整理変更後、全部の動作確認はしていないので、何かあればコメントで指摘して下さい。

# 便利キーマップ

基本的に、<kbd>cmd-t</kbd> や、<kbd>cmd-r</kbd> で表示されるミニエディタの絞込時に、`;` セミコロンを入力する事は無いので、これを決定(`core:confirm`)として使っている。
また、<kdb>ctrl-u</kbd> で行頭まで文字列を一気に消せるようにもしている。

```coffeescript:keymap.cson
'.select-list':
  ';':      'core:confirm'
  'ctrl-u': 'editor:delete-to-beginning-of-line'
```

# Atom 自体の Hack

## ATOM_DEV_RESOURCE_PATH 環境変数

atom コマンドのヘルプを見れば書いてあるが、`ATOM_DEV_RESOURCE_PATH` という環境変数がある。
ここに指定したPATHは、dev モードで起動した時、source をロードする PATH として使われる。
つまり、直接 coffee の source を変更して、dev モードで起動するだけで、動作を直ぐに確認出来る。

デフォルトでは、`~/github/atom` が使われるので、`~/github` 直下に atom を clone しておけば、
`$ atom -d` で起動するだけで良い。

```shell-session
$ atom --help
Atom Editor v1.0.3-9774b20

Usage: atom [options] [path ...]

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.

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`.
<以下省略>
```

# TextEditor 内で使われている単語リストを取得する

[lazy-motion](https://atom.io/packages/lazy-motion) 内で似たようなことをやっている。

二回目の get 以降は、キャッシュを返すので、取得しなおしたければ、destroy() 後、get すればいける(はず)。

```coffeescript
# 指定したパターンにマッチするテキストのリストを返す
getTokenProvider: (editor, pattern) ->
  matches = null

  get: ->
    return matches if matches
    matches = []
    editor.scan pattern, ({matchText}) =>
      matches.push matchText
    matches

  destroy: ->
    matches = null
```

Usage

```coffeescript
editor = atom.workspace.getActiveTextEditor()
@tokenProvider ?= @getTokenProvider(editor, /\w+/g)
console.log @tokenProvider.get()
@tokenProvider.destroy()
@tokenProvider = null
```

# Markdown 内のソースコード部分の Grammar スコープ名を得る。

例えば、Markdown 内の、CoffeeScript のコード部分にカーソルがある時、`source.coffee` のようなスコープ名を取得したい。

単に以下の様にしたのでは取得できない。

```coffeescript
scopeName = atom.workspace.getActiveTextEditor().getGrammar().scopeName
scopeName # => 'source.gfm'
```

方法

```coffeescript
# カーソル位置の ScopeDescriptor を得る
getCursorScope: ->
  editor = atom.workspace.getActiveTextEditor()
  [rowStart, rowEnd] = editor.getLastSelection().getBufferRowRange()
  editor.scopeDescriptorForBufferPosition([rowStart, 0])

# 渡された ScopeDescriptor から最もカーソル位置に近い、Grammar Scope を得る。
# scopesArray は、`comment.line.number-signe.coffee` のような scope も含まれるので
# grammarScopename に絞って探す。
getGrammarScopeNameForScope: (scope)->
  scopeNames = scope.getScopesArray().slice().reverse()
  for scopeName in scopeNames when scopeName in @getGrammarScopeNames()
    return scopeName
  null

# 利用できる Grammarの ScopeName のリストを得る。
getGrammarScopeNames: ->
  atom.grammars.getGrammars().map (grammar) ->
    grammar.scopeName
```

Usage

```coffeescript
scopeName = @getGrammarScopeNameForScope @getCursorScope()
```


# 画面をフラッシュする

注意を促したいときに。Screen Beep 的なもん。
ScreenRow の範囲でフラッシュしてるので、Fold があっても大丈夫。

* coffee

```coffeescript
flashScreen: (editor, duration=150) ->
  [startRow, endRow] = editor.getVisibleRowRange().map (row) ->
    editor.bufferRowForScreenRow row

  range = [[startRow, 0], [endRow, Infinity]]
  marker = editor.markBufferRange range,
    invalidate: 'never'
    persistent: false

  flashingDecoration = editor.decorateMarker marker,
    type: 'highlight'
    class: 'sample-flash'

  setTimeout =>
    flashingDecoration.getMarker().destroy()
  , duration
```

* less

```css
@import "syntax-variables";
atom-text-editor::shadow {
  .sample-flash .region {
    background-color: @syntax-selection-flash-color;
  }
}
```

* Usage

```coffeescript
@flashScreen atom.workspace.getActiveTextEditor(), 200
```

# ヒストリ管理

ヒストリ管理をしたいことは良くある。
簡単版.

```coffeescript
_ = require 'underscore-plus'

getHistoryManager: (maxSize) ->
  entries = []
  index = -1

  get: (direction) ->
    if direction is 'prev'
      index = (index + 1) % entries.length
    else if direction is 'next'
      index -= 1
      index = (entries.length - 1) if index < 0
    entries[index]

  save: (entry) ->
    return if _.isEmpty(entry)
    entries.unshift entry
    entries = _.uniq entries # Eliminate duplicates
    if entries.length > maxSize
      entries.splice maxSize

  reset: ->
    index = -1

  destroy: ->
    entries = null
    index = null
```

Usage

```coffeescript
historySize = 10
@historyManager = @getHistoryManager historySize

# 保存
@historyManager.save 'foo'

# 取り出し
if entry = @historyManager.get 'prev'
  console.log entry

# 不要になったら開放
@historyManager.destroy()
```


#  画面に見えている、TextEditor を得る。

path はオプションで、 同じPATHの TextEditor のみを取得したい場合に指定する。

```coffeescript
getVisibleEditors: ({path}={}) ->
  editors = atom.workspace.getPanes()
    .map    (pane)   -> pane.getActiveEditor()
    .filter (editor) -> editor?

  if path
    editors = editors.filter (editor) ->
      editor.getPath() is path
  editors
```

Usage

```coffeescript
# case-1
editors = @getVisibleEditors()

# case-2
editor = atom.workspace.getActiveTextEditor()
editors = @getVisibleEditors path: editor.getPath()
```

# TextEditor の状態を保存して復旧する

エディタの状態を復旧する関数を返す関数。
Emacs には save-excursion という関数(スペシャル・フォーム)があって、いろいろやった後にエディタの見た目の状態を一発で元に戻すことができて便利だった。

それには全く及ばないが、screenTop と、fold を元の状態に戻せる。
スクロールしたり、fold を開いたりしても、元に戻せるという事。

```coffeescript
# Return function to restore editor state.
saveEditorState: (editor) ->
  scrollTop = editor.getScrollTop()
  foldStartRows = editor.displayBuffer.findFoldMarkers().map (m) =>
    editor.displayBuffer.foldForMarker(m).getStartRow()
  ->
    for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row)
      editor.foldBufferRow row
    editor.setScrollTop scrollTop
```

Usage

```coffeescript
editor = atom.workspace.getActiveTextEditor()
@restoreEditorState = @saveEditorState editor

# <このあと, scroll や unfold が必要な処理>

@restoreEditorState()
```