CodeMirrorでEmoji AutoCompleteをする

":"に続けて打ち込むと自動で補完してくれるようになります。

demo

EmojiリストはGitHub APIから取得しています。

必要なパッケージ

  • 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を使用する必要があるので注意。

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}:`, displayText: `<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)

参考記事

Show comments

Adsense

Share

  • このエントリーをはてなブックマークに追加

About

どこにでもいる平凡なプログラムを書く人間のログ。

Recently

Tags

Pages

-->