Sergei_Erjemin
@Sergei_Erjemin
Улыбайся, будь самураем...

Как в админке Django настроить codemirror на подсветку нескольких языков?

Для одного своего пет-проекта понадобилось использовать подсветку синтаксиса для одного из TextField полей. Для этого отлично подходит codemirror. Попробовав несколько «батареек» с виджетами, решил отказаться от внешних зависимостей и прикрутить codemirror самостоятельно (если честно, батарейки у меня просто не заработали, зато нашелся вполне рабочий рецепт).

В моделях есть TbTemplate с полями для описания шаблонов. Оттуда бэкенд берёт шаблоны для рендера, а само TextField-поле с шаблоном — szJinjaCode.

Делаю вот так.

В admin.py:

class TemplateAdminForm(forms.ModelForm):
    class Meta:
        model = TbTemplate
        fields = "__all__"
        widgets = {
            'szJinjaCode': forms.Textarea(attrs={'class': 'jinja2-editor'})
        }


# -- Управление шаблонами
@admin.register(TbTemplate)
class AdminTemplate(admin.ModelAdmin):
    class Media:
        # настройка подключения codemirror
        css = {
            'all': (
                # '/static/codemirror-5.65.13/doc/docs.css',
                '/static/codemirror-5.65.13/lib/codemirror.css',
                '/static/codemirror-5.65.13/addon/hint/show-hint.css',
                '/static/codemirror-5.65.13/addon/lint/lint.css',
                '/static/codemirror-5.65.13/theme/rubyblue.css',
            )
        }
        js = (
            '/static/codemirror-5.65.13/lib/codemirror.js',
            '/static/codemirror-5.65.13/addon/hint/show-hint.js',
            '/static/codemirror-5.65.13/addon/hint/xml-hint.js',
            '/static/codemirror-5.65.13/addon/hint/html-hint.js',
            '/static/codemirror-5.65.13/mode/xml/xml.js',
            '/static/codemirror-5.65.13/mode/javascript/javascript.js',
            '/static/codemirror-5.65.13/mode/css/css.js',
            '/static/codemirror-5.65.13/mode/htmlmixed/htmlmixed.js',
            '/static/codemirror-5.65.13/mode/jinja2/jinja2.js',
            # '/static/codemirror-5.65.13/addon/runmode/colorize.js',
            # '/static/codemirror-5.65.13/addon/hint/html-hint.js',
            # '/static/codemirror-5.65.13/addon/lint/lint.js',
            # '/static/codemirror-5.65.13/addon/lint/html-lint.js',
            '/static/js/codemirror/init_jinja2.js'
        )

    # подключение формы TemplateAdminForm
    form = TemplateAdminForm
    # далее остальные описания для админки модели TbTemplate
    # ...
    # ...


Как видно из кода, нужно ещё в статику положить инициализационный файл /static/js/codemirror/init_jinja2.js. Собственно он взять из рецепта и немного модифицирован:
// Этот файл нужен для инициализации jinja-редактора шаблонов codemirror в админке
// рецепт: https://webdevblog.ru/redaktirovanie-json-polej-cherez-django-adminku/
(function(){
    var $ = django.jQuery;
    $(document).ready(function(){
        $('.jinja2-editor').each(function(idx, el){
            function getSelectedRange() {
                return { from: editor.getCursor(true), to: editor.getCursor(false) };
            }
            var editor = CodeMirror.fromTextArea(el, {
                lineNumbers: true,
                tabSize: 2,
                // mode: 'text/html',
                mode: 'text/jinja2',
                gutters: ['CodeMirror-lint-markers'],
                theme: 'rubyblue',
                lint: true,
            });
            CodeMirror.commands["selectAll"](editor);
            var range = getSelectedRange();
            editor.autoFormatRange(range.from, range.to);
            range = getSelectedRange();
            editor.commentRange(false, range.from, range.to);
        });
    });
})();


Запускаем, и всё работает. Точнее работает подсветка тегов и переменных Jinja2. Если в init_jinja2.js поменять mode: 'text/jinja2' на mode: 'text/html' — будет подсвечиваться html-код, но не будут подсвечена разметка Jinja2… Хочется чтобы подсвечивалось оба кода. Еще лучше если ещё подсвечивать CSS, JavaScript и JSON. Как заставить codemirror подсвечивать одновременно это всё?

P.S. Напрашивается вариант — взять в codemirror нужные файлы «модов» из каталога /static/codemirror-5.65.13/mode/ и собрать из них свою «моду», но это калечащее решение для самого codemirror. При его обновлении всё порушится и эту самодельную «моду» надо будет собирать заново. Это, мне кажется, неправильно. Хочется включить несколько «мод» (режимов подсветки) одновременно штатным способом. Как?
  • Вопрос задан
  • 154 просмотра
Решения вопроса 1
Sergei_Erjemin
@Sergei_Erjemin Автор вопроса
Улыбайся, будь самураем...
Для наложения режимов подсветки в CodeMirror нужно использовать CodeMirror.multiplexingMode. Причем последовательно для каждой пары открывающий/закрывающий тег. Таким образом для смешивания html+jinja2 в админке Django получится вот такой /static/js/codemirror/init_jinja2.js:

// Этот файл нужен для инициализации html+jinja-редактора шаблонов codemirror в админке Django
// рецепт написал сам: https://qna.habr.com/q/1284408
(function () {
  var $ = django.jQuery;
  $(document).ready(function () {
    // Включаем "темную" или "светлую" тему в зависимости от настроек браузера пользователя
    var theme_is = 'idea';
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) theme_is = 'rubyblue';  // dark mode

    // Включаем подсветку jinja-тегов {{...}} внутри html
    CodeMirror.defineMode("html+jinja2{}", function (config) {
      return CodeMirror.multiplexingMode(
        CodeMirror.getMode(config, "text/html"), {
          open: "{{", close: "}}",
          mode: CodeMirror.getMode(config, "jinja2"),
          parseDelimiters: true,
        }
        );
    });

    // Включаем подсветку jinja2-тегов {%...%}
    CodeMirror.defineMode("html+jinja2%%", function (config) {
      return CodeMirror.multiplexingMode(
        CodeMirror.getMode(config, "html+jinja2{}"), {
          open: "{%", close: "%}",
          mode: CodeMirror.getMode(config, "jinja2"),
          parseDelimiters: true,
        }
        );
    });

    // Включаем подсветку jinja2-комментариев {#...#}
    CodeMirror.defineMode("html+jinja2", function (config) {
      return CodeMirror.multiplexingMode(
        CodeMirror.getMode(config, "html+jinja2%%"), {
          open: "{#", close: "#}",
          mode: CodeMirror.getMode(config, "jinja2"),
          parseDelimiters: true,
        }
        );
    });

    // инициализация codemirror
    $('#code_editor').each(function (idx, el) {
      var editor = CodeMirror.fromTextArea(el, {
        lineNumbers: true,
        tabSize: 2,
        mode: 'html+jinja2',
        gutters: ['CodeMirror-lint-markers'],
        theme: theme_is,
        lint: true,
        autoCloseTags: true,
        matchBrackets: true,
      });
      editor.setSize('120em', 'auto');
      editor.addKeyMap({
        'Ctrl-S': function (cm) {$(el).closest('form').submit(); },     // submit
        'Ctrl-F': 'findPersistent',       // поиск
      });
    });
  });
})();
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы