Skip to content

Instantly share code, notes, and snippets.

@bilobom
Last active February 23, 2024 17:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bilobom/8076d2d4ff6bda55fc2a13aa5e803eea to your computer and use it in GitHub Desktop.
Save bilobom/8076d2d4ff6bda55fc2a13aa5e803eea to your computer and use it in GitHub Desktop.
codemirror6 react custom autocomplete

In this gist we add Custom HTML and JSON autocomplete without overriding the original autocomplete. Each language support in code mirror exports nameLanguage (eg: htmlLanguage) to allow you to add custom autocomplete.

import React, { useMemo } from 'react'
import CodeMirror from '@uiw/react-codemirror'
import { json, jsonLanguage } from '@codemirror/lang-json'
import { html, htmlLanguage } from '@codemirror/lang-html'
import { syntaxTree } from '@codemirror/language'

const getExtension=(mode)=>{
  switch (mode) {
    case "json":
      return [
        json(),
        jsonLanguage.data.of({
          autocomplete: function myCompletions(context) {
            const word = context.matchBefore(/\w*/);
            if (word.from === word.to && !context.explicit) return null;
            return {
              from: word.from,
              options: [
                {
                  label: "some_key",
                  apply: '"some_key":"value",',
                  info: "key value pair auto complete",
                },
                {
                  label: "some_key",
                  apply: '"some_key":"value",',
                  info: "yet another key value pair auto complete",
                },
              ],
            };
          },
        }),
      ];
    case "html":
      return [
        html(),
        htmlLanguage.data.of({
          autocomplete: function htmlCompletionSource(context) {
            const { state, pos } = context;
            let around = syntaxTree(state).resolveInner(pos),
              tree = around.resolve(pos, -1);
            for (
              let scan = pos, before;
              around === tree && (before = tree.childBefore(scan));

            ) {
              const last = before.lastChild;
              if (!last || !last.type.isError || last.from < last.to) break;
              around = tree = before;
              scan = last.from;
            }
            if (
              tree.name === "TagName" &&
              !(tree.parent && /CloseTag$/.test(tree.parent.name))
            ) {
              return {
                from:tree.from,
                to:pos,
                options: [
                    {
                        label:"audio",
                        info:"HTML audio tag",
                    }
                ],
                span: /^\/?[:\-\.\w\u00b7-\uffff]*$/
              };
            } 
            else if (
                (context.explicit &&
                  (tree.name === 'OpenTag' || tree.name === 'SelfClosingTag')) ||
                tree.name === 'AttributeName'
              ) {
                return {
                  from: tree.name === 'AttributeName' ? tree.from : pos,
                  to: pos,
                  options:  [
                    {
                        label:"aria-label",
                        info:"HTML aria-label attribute",
                    }
                ],
                  span: /^[:\-\.\w\u00b7-\uffff]+$/
                }
              }
          },
        }),
      ];
  }
}
const Editor = (props) => {
  const { value,mode } = props

  return (
    <div className='editorContainer'>
      <CodeMirror
        mode={mode}
        value={value}
        extensions={getExtension(mode)}
      />
    </div>
  )
}

export default Editor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment