これは はてなエンジニアアドベントカレンダー2016 23日目の記事です。
qiita.com
developer.hatenastaff.com
昨日は id:takuya-a さんの文字列アルゴリズムの学びかたでした。
こんにちは、はてなでアプリケーションエンジニアとしてWebサービスやAndroidアプリ(たまにiOSアプリ)を開発しているid:takuji31です。
私は普段VimでPerlを書いているのですが、最近AtomでPerlを書きたくなってAtomの自動補完プラグインである「autocomplete-plus」のPerl用Providerを書いているので、今日はそのことについて話します。
先に謝罪しておきますと、本来はこのエントリーで公開しましたと告知する予定でしたが、まだ実用的なクオリティーには達していないので、開発中とお知らせするだけになります 🙇
github.com
Atomとは(恐らくこの記事を読むような方はご存知だとは思いますが)、Githubが公開しているオープンソースのテキストエディターです。
atom.io
A hackable text editor
とある通り、プラグインを書くことでかなり自由にHackすることができます。
autocomplete-plus
autocomplete-plusはAtomの自動補完プラグインです。
github.com
最近のバージョンのAtomに標準でバンドルされていて、インストールするだけで様々な言語のコード補完を行うことができます。
また、標準で用意されている言語以外にも、Providerを用意することで自分でコード補完の候補を作ることができます。
既に存在するProviderの一覧はGitHubのautocomplete-plusのWikiに一覧があります
github.com
見てみると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
menus
styles
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で書いています。
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としては機能します。
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
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
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が好きなエンジニアを募集しています!
hatenacorp.jp
明日の担当は id:tarao です、お楽しみに!