CodeMirrorでEmoji AutoCompleteをするに書いたけどQiitaにも転載。
":"に続けて打ち込むと自動で補完してくれるようになります。
ちゃんとブラウザ上で動いている様子です。
EmojiリストはGitHub APIから取得しています。
LocksというLocalStorageを使ったスライドエディタを作っているときにできた副産物です。
必要なパッケージ
- CodeMirror
- show-hint addonのコードを編集するのでソースコードが必要
- Axios
パッケージの読み込み方法は各自におまかせします(scriptタグでもビルドツールでも構いません)。
やり方
// sample data
let emojiList = ['apple:', 'abc:', 'axz:', 'bee:', 'beam:', 'bleach:']
let emojiComplete = function(cm) {
CodeMirror.showHint(cm, function() {
let cur = cm.getCursor(), token = cm.getTokenAt(cur)
let start = token.start, end = cur.ch, word = token.string.slice(0, end - start)
let ch = cur.ch, line = cur.line
let currentWord = token.string
while (ch-- > -1) {
let t = cm.getTokenAt({ch, line}).string
if (t === ':') {
let filteredList = emojiList.filter((item) => {
return item.indexOf(currentWord) == 0 ? true : false
})
if (filteredList.length >= 1) {
return {
list: filteredList,
from: CodeMirror.Pos(line, ch),
to: CodeMirror.Pos(line, end)
}
}
}
currentWord = t + currentWord
}
}, { completeSingle: false })
}
cm.on('change', emojiComplete)
ひとまずemoji画像のないシンプルな例です。cmはCodeMirrorインスタンスです。
CodeMirrorのchangeイベントにshowHintを仕込んで実現しています。
showHintの中身が肝です。
最初の4行はshowHintを書く際の決まり文句のようなものでカーソル位置や対象の単語情報を変数に記録しています。
while文内では、単語を一文字ずつ遡っていって":"が見つかったら補完候補を絞り込んで、補完候補が存在する場合は表示するようにしています。
showHintの戻り値は、補完のリストと補完する位置を指定します。位置は今の位置から単語終了位置までになります。
これで自動的に":"のあとに文字を入れると補完候補が表示されます。
showHint関数に指定している、completeSingle: falseは必須です。細かい理由はわかりませんがこれがないと無限ループするようです。
emojiListが末尾に":"が付いて不格好なのは、補完完了時に末尾の":"まで補完されるようにしているからですがこれは回避できます、これは以下の内容で説明します。
Emojiのリスト取得
GitHub APIにEmoji一覧を取得するAPIがあるので利用します。
Emojis | GitHub Developer Guide
先程の例のemojiListをapiで取得したものに置き換えれば完全なリストになります。
let res = await axios.get('https://api.github.com/emojis')
if (res.status !== 200) {
console.log('Error...')
return
}
let emojiList = []
for (let key in res.data) {
emojiList.push(`${key}:`)
}
Emoji画像の表示
show-hint addonの補完候補には文字列しか表示できません。
実際にCodeMirrorのaddon/hint/show-hint.jsを見てみると、文字列リスト or {text: str, displayText: str}のリストを指定する仕様になっています。
画像を表示させるようにshow-hint.jsを書き換えます。
if (cur.render) cur.render(elt, data, cur);
- else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+ else elt.innerHTML = cur.displayText
elt.hintId = i;
219行目あたりです。
displayTextで指定する補完候補表示をinnerHTMLで表示できるようにしました。
{text: str, displayText: str}のリストでの補完候補の指定は、textは実際に補完する際に使われる文字列でdisplayTextが補完候補を表示するときに使われる文字列になるので、emojiListを以下のようにします。
let res = await axios.get('https://api.github.com/emojis')
if (res.status !== 200) {
console.log('Error...')
return
}
let emojiList = []
for (let key in res.data) {
emojiList.push({text: `${key}:`, displayText: `<img width="15" height="15" src="${res.data[key]}" alt="icon" async></img> ${key}`})
}
displayTextをGitHub APIで取得した画像を小さくしたもの + Emoji名の形にします。
これで冒頭のGifキャプチャーのように表示されるようになります。
かなりの量の画像を取得するのでimgタグにはasyncオプションをつけています。
完全なコード
書き換えたshow-hint.jsを使用する必要があるので注意。
render
function を利用すれば show-hint.js の書き換えは不要。
参考: https://github.com/codemirror/CodeMirror/blob/5.33.0/addon/hint/show-hint.js#L212
let cm = CodeMirror(/* Dom Element */, {
// Options
})
//
// Emoji Complete
//
let res = await axios.get('https://api.github.com/emojis')
if (res.status !== 200) {
console.log('Error...')
return
}
let emojiList = []
for (let key in res.data) {
emojiList.push({
text: `${key}:`,
render: (element) => {
element.innerHTML = `<img width="15" height="15" src="${res.data[key]}" alt="icon" async></img> ${key}`})
}
})
}
let emojiComplete = function(cm) {
CodeMirror.showHint(cm, function() {
let cur = cm.getCursor(), token = cm.getTokenAt(cur)
let start = token.start, end = cur.ch, word = token.string.slice(0, end - start)
let ch = cur.ch, line = cur.line
let currentWord = token.string
while (ch-- > -1) {
let t = cm.getTokenAt({ch, line}).string
if (t === ':') {
let filteredList = emojiList.filter((item) => {
return item.text.indexOf(currentWord) == 0 ? true : false
})
if (filteredList.length >= 1) {
return {
list: filteredList,
from: CodeMirror.Pos(line, ch),
to: CodeMirror.Pos(line, end)
}
}
}
currentWord = t + currentWord
}
}, { completeSingle: false })
}
cm.on('change', emojiComplete)