これは はてなエンジニアアドベントカレンダー2016 23日目の記事です。
昨日は id:takuya-a さんの文字列アルゴリズムの学びかたでした。
こんにちは、はてなでアプリケーションエンジニアとしてWebサービスやAndroidアプリ(たまにiOSアプリ)を開発しているid:takuji31です。
私は普段VimでPerlを書いているのですが、最近AtomでPerlを書きたくなってAtomの自動補完プラグインである「autocomplete-plus」のPerl用Providerを書いているので、今日はそのことについて話します。
先に謝罪しておきますと、本来はこのエントリーで公開しましたと告知する予定でしたが、まだ実用的なクオリティーには達していないので、開発中とお知らせするだけになります 🙇
Atomとは
Atomとは(恐らくこの記事を読むような方はご存知だとは思いますが)、Githubが公開しているオープンソースのテキストエディターです。
A hackable text editor
とある通り、プラグインを書くことでかなり自由にHackすることができます。
autocomplete-plus
autocomplete-plusはAtomの自動補完プラグインです。
最近のバージョンのAtomに標準でバンドルされていて、インストールするだけで様々な言語のコード補完を行うことができます。
また、標準で用意されている言語以外にも、Providerを用意することで自分でコード補完の候補を作ることができます。
既に存在するProviderの一覧はGitHubのautocomplete-plusのWikiに一覧があります
見てみるとPerl用がないですね、ググってみた感じもないようでしたので、これは作るしかないと思いました。
Providerを作る
雛形を自動生成する
ProviderはAtomのPackageとして作る必要があります。
Atomでは開発用にPackageを生成してくれるメニューがあります、メニューの Packages
-> PackageGenerator
-> Generate Atom Package
を実行しましょう。
コマンドパレットを開いて Package Generator: Generate Package
を探して実行してもよいです。
実行するとPackageの作成先を指定するダイアログが出ますので、適当な名前を決めて入力します。
決定すると指定したパスにサンプルコードと共にPackageが生成されます。
hatena-advent-calendar-2016/ ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── keymaps │ └── hatena-advent-calendar-2016.json ├── lib │ ├── hatena-advent-calendar-2016-view.js │ └── hatena-advent-calendar-2016.js ├── menus │ └── hatena-advent-calendar-2016.json ├── package.json ├── spec │ ├── hatena-advent-calendar-2016-spec.js │ └── hatena-advent-calendar-2016-view-spec.js └── styles └── hatena-advent-calendar-2016.less
以下のファイルとディレクトリはProviderには不要なので消します。
keymaps
- このPackageのキーマップを定義するファイル
menus
- メニューのエントリーを定義するファイル
styles
- Packageのスタイルを定義するファイル
lib/*.js
- サンプルコードですが(Providerでは)一切使わないので消します。
Packageに必要な設定
不要なファイルを整理したら、 package.json
の一番上の階層に以下の設定を追加します。
{ "providedServices": { "autocomplete.provider": { "versions": { "2.0.0": "provide" } } } }
provide
の部分はJS側で定義するメソッド名なので、自由に決めて構いません。
Packageのメインオブジェクトを作る
lib
以下のPackage名と同じjsファイルからexportしたオブジェクトがプラグイン機構の本体になります。
JavaScript以外にもCoffeeScriptも直接使うことができますが、私は業務でTypeScriptを使っていることもあり、TypeScriptで書いています。
/// <reference path="../typings/bundle.d.ts" /> import {Config} from "./config"; import {Provider} from "./provider"; import {UseAndRequireCompletionProvider} from "./use-and-require-completion-provider"; class AutocompletPerlProvider { providers: Provider[] = null config = Config.config activate() { } deactivate() { this.providers = null } provide() { if (this.providers === null) { this.providers = [new UseAndRequireCompletionProvider()]; } return this.providers } } export = new AutocompletPerlProvider()
このように provide
メソッドの中で実際の補完候補を提供するProviderのインスタンスを作って返してやります。Providerは複数指定可能です。
設定項目を追加
メインのオブジェクトに config
フィールドを追加することで、そこに定義されている設定項目がAtomの設定画面に自動的に追加されます。
先ほどのコードでは config
フィールドの中身は Config.config
を参照しているので、その中身を見てみましょう。
class Config { static config = { perlPath: { type: 'string', default: 'perl' }, cartonPath: { type: 'string', default: 'carton' }, useCarton: { type: 'boolean', description: 'Use carton when building completion suggestions. only effects when cpanfile and carton executable exists.', default: false, } } static get perlPath():String { return atom.config.get('autocomplete-perl.perlPath'); } static get cartonPath():String { return atom.config.get('autocomplete-perl.cartonPath'); } static get useCarton():boolean { return atom.config.get('autocomplete-perl.useCarton'); } } export { Config }
上記のコードでは3つの設定項目が指定されています。
このような定義を作成することで、Atomの設定画面にあるPackageの設定項目が自動的に生成されて以下のようになります。
このコードでは設定の定義以外にも、設定値を取り出すのに便利なプロパティーを定義しています。このようにしておくと、実際のコード側で簡単に取り出せて便利でしょう。
Providerを作る
Providerは決まったメソッドとフィールドを持ったクラスである必要があります。
その仕様については、autocomplete-plusのWikiにまとめられています。
Provider API · atom/autocomplete-plus Wiki · GitHub
最低限 selector
フィールドと getSuggestions
メソッドがあればProviderとしては機能します。
/// <reference path="../typings/bundle.d.ts" /> import {Provider, SuggestionInfo} from "./provider"; import {ISuggestion} from "./suggestion"; import {Range, Point} from "atom"; class UseAndRequireCompletionProvider extends Provider { selector : string = ".source.perl" getSuggestions(info : SuggestionInfo) : Promise<ISuggestion> { return new Promise((resolve) => { var suggestions = // ここで補完候補を生成する resolve(suggestions) }); } } export { UseAndRequireCompletionProvider }
atom-autocomplete-perl
では型でこの辺りの制約をはっきりさせておきたかったので、ベースのクラスやインターフェイスを別ファイルで定義しました。
provider.ts
/// <reference path="../typings/bundle.d.ts" /> import {ISuggestion} from './suggestion'; interface SuggestionInfo { editor: AtomCore.IEditor; bufferPosition: TextBuffer.IPoint; scopeDescriptor: AtomCore.ScopeDescriptor; prefix: string; activatedManually: boolean; } abstract class Provider { selector : string = ".source.perl" abstract getSuggestions(info : SuggestionInfo) : Promise<ISuggestion[]> } export { Provider, SuggestionInfo }
suggestion.ts
/// <reference path="../typings/bundle.d.ts" /> type SuggestionType = 'variable'|'constant'|'property'|'value'|'method'|'function'|'class'|'type'|'keyword'|'tag'|'snippet'|'import'|'require'; interface ISuggestion { displayText?: string replacementPrefix?: string type?: SuggestionType; leftLabel?: string leftLabelHTML?: string rightLabel?: string rightLabelHTML?: string className?: string iconHTML?: string description?: string descriptionMoreURL?: string } class TextSuggestion implements ISuggestion { constructor(public snippet: string, public type: SuggestionType) { } } class SnippetSuggestion implements ISuggestion { constructor(public text: string) { } } export { ISuggestion, TextSuggestion, SnippetSuggestion, SuggestionType }
この定義のおかげで、簡単にProviderを増やすことができますね。
あとはひたすら getSuggestions
に渡ってくる入力の情報から非同期に補完候補を生成してresolveするだけです。
とは言うものの、今の時点でまだこれは使っていません、なぜなら簡単なキーワードだけなら自動的に補完候補を生成してくれる便利APIがあるのです。
SymbolProvider Config API
簡単なキーワードなら、SymbolProvider Config API を使うことで補完候補を生成できます。
SymbolProvider Config API · atom/autocomplete-plus Wiki · GitHub
たとえばPerlのビルトイン関数は settings/language-perl.cson
に以下のように書くだけです。
'.source.perl': autocomplete: symbols: builtin: suggestions: [ 'abs' 'accept' 'alarm' 'atan2' 'bind' 'binmode' 'bless' // ... 'waitpid' 'wantarray' 'warn' 'write' 'y' ]
これだけであとは勝手に補完できるようになります。
ビルトイン関数は最初Providerを作って補完できるようにしていましたが、こちらの設定に変えました。
atom-autocomplete-perl の今後について
このProviderは、今のところビルトイン関数の補完だけ動きます。
今後以下のような機能の提供を予定しています。
- package名補完
- クラスメソッド補完
- (構想段階ですが)
Smart::Args
やData::Validator
でバリデーションした変数の型を雑に推測してそれっぽい補完候補を返す
ひとまずpackage名補完だけ追加したら、全国のPerl Mongerの皆様が利用できる状態にしたいなぁと思っています。
まとめ
AtomのPackageは簡単に作ることができました。node.jsを使うことができますので、npmにある大量の資産を活かしたり、他の言語のコードをシステム経由で実行することもできます。
あなたもこの機会にAtomをHackしてみませんか?
はてなではJavaScriptやAltJSが好きなエンジニアを募集しています!
明日の担当は id:tarao です、お楽しみに!