Подсветка синтаксиса несколькими строками javascript

    Да, я знаю, что такое синтаксический анализ. И знаю много разных библиотек для подсветки чего угодно. Только это всё не то, когда надо подсветить простенький примерчик, не содержащий всяких кодоизвращений. И уж совсем негоже тянуть для этого много-много байт _правильно_ разбирающих _любой_ код.

    Для случаев без кодоизврата (а их большинство) можно использовать такой код:
    code = code
    // ключевые слова (список неполон, написал, что в голову пришло)
    .replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
    '<span class="kwrd">$1</span>$2')
    // всякие скобочки
    .replace(/(\{|\}|\]|\[|\|)/gi,'<span class="kwrd">$1</span>')
    // однострочные комментарии
    .replace(/(\/\/[^\n\r]*(\n|\r\n))/g,'<span class="comm">$1</span>')
    // строки
    .replace(/('.*?')/g,'<span class="str">$1</span>')
    // функции (когда после идентификатора идет скобка)
    .replace(/([a-z\_\$][a-z0-9_]*)\(/gi,'<span class="func">$1</span>(')
    // не люблю восьмизначные табы, пусть лучше будет 4 пробела
    .replace(/\t/g,'    ');




    CSS


    .str{color:red}/* Строки красные */
    .func{color:blue}/* Юзер-функции синие */
    .comm{color:orange}/* Комменты оранжевые */
    .kwrd{font-weight:bold}/* Ключевые слова полужирные */
    .str span{color:red;font-weight:normal}/* Всё внутри строки — строка */
    .comm span{color:orange;font-weight:normal}/* Всё внутри комментария — комментарий */


    Пример


    Подсветка синтаксиса - upload images with Picamatic
    Обратите внимание на баг в последней строке. Об этом разговор далее

    Область применения, баги


    Данный код заточен на javascript, о чём можно судить по ключевым словам. Сделать код понимающим любой Си-подобный язык не представляет труда.

    Возиться с многострочными комментариями я не стал, как и с многострочными строками, ибо не так уж частно многострочные комменты используются в примерах, а к многострочным строкам в javascript я как-то не привык.

    Еще чем плох данный подход — текст «плоский», то есть этот, с позволения сказать, анализатор, считает слово for внутри строки или комментария таким же ключевым словом, как и все их. Нас это не устраивает, поэтому используются css правила .str span и .comm span, дабы возложить на плечи CSS распознавание блочной структуры кода. Одна проблема возникает — когда строка содержит комментарий, который содержит ключевое слово или пользовательскую функцию. В этом случае мы не взирая на окончание строки всё считаем строкой (до символа перевода строки).

    И ещё, двойных кавычек не существует. Можно добавить еще один replace:
    .replace(/(".*?")/g,'<span class="str">$1</span>')

    Но меня от этого что-то удерживает.

    Бонус: плагин для jQuery



    chainable, multiple

    	(function($){
    
    	$.fn.syntax = function(){
    		return this.each(function(){
    			var jqCode = $(this);
    			var code = jqCode.text();
    			code = code
    			.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|case|switch)([^a-z0-9\$_])/gi,'<span class="kwrd">$1</span>$2')
    			.replace(/(\{|\}|\]|\[|\|)/gi,'<span class="kwrd">$1</span>')
    			.replace(/(\/\/[^\n\r]*(\n|\r\n))/g,'<span class="comm">$1</span>')
    			.replace(/('.*?')/g,'<span class="str">$1</span>')
    			.replace(/([a-z\_\$][a-z0-9_]*)\(/gi,'<span class="func">$1</span>(')
    			.replace(/\t/g,'    ');
    			jqCode.html(code);
    		});
    	}
    
    	})(jQuery);
    
    	// пример вызова
    	$('pre#code').syntax(); // подсветка конкретного блока pre с id=code
    	$('pre').syntax(); // подсветка всех pre


    Усовершенствованный вариант от etcdema


    function Syntax(code){
    	var comments	= [];	// Тут собираем все каменты
    	var strings		= [];	// Тут собираем все строки
    	var res			= [];	// Тут собираем все RegExp
    	var all			= { 'C': comments, 'S': strings, 'R': res };
    	var safe		= { '<': '<', '>': '>', '&': '&' };
    
    	return code
    	// Маскируем HTML
    		.replace(/[<>&]/g, function (m)
    			{ return safe[m]; })
    	// Убираем каменты
    		.replace(/\/\*[\s\S]*\*\//g, function(m)
    			{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~';   })
    		.replace(/([^\\])\/\/[^\n]*\n/g, function(m, f)
    			{ var l=comments.length; comments.push(m); return f+'~~~C'+l+'~~~'; })
    	// Убираем regexp
    		.replace(/\/(\\\/|[^\/\n])*\/[gim]{0,3}/g, function(m)
    			{ var l=res.length; res.push(m); return '~~~R'+l+'~~~';   })
    	// Убираем строки
    		.replace(/([^\\])((?:'(?:\\'|[^'])*')|(?:"(?:\\"|[^"])*"))/g, function(m, f, s)
    			{ var l=strings.length; strings.push(s); return f+'~~~S'+l+'~~~'; })
    	// Выделяем ключевые слова
    		.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
    			'<span class="kwrd">$1</span>$2')
    	// Выделяем скобки
    		.replace(/(\{|\}|\]|\[|\|)/gi,
    			'<span class="gly">$1</span>')
    	// Выделяем имена функций
    		.replace(/([a-z\_\$][a-z0-9_]*)[\s]*\(/gi,
    			'<span class="func">$1</span>(')
    	// Возвращаем на место каменты, строки, RegExp
    		.replace(/~~~([CSR])(\d+)~~~/g, function(m, t, i)
    			{ return '<span class="'+t+'">'+all[t][i]+'</span>'; })
    	// Выставляем переводы строк
    		.replace(/\n/g,
    			'<br/>')
    	// Табуляцию заменяем неразрывными пробелами
    		.replace(/\t/g,
    			'&nbsp;&nbsp;&nbsp;&nbsp;');
    }
    Тут понадобятся другие стили:
    .S{color:red}/* Строки красные */
    .func{color:blue}/* Юзер-функции синие */
    .C{color:orange}/* Комменты оранжевые */
    .kwrd{font-weight:bold}/* Ключевые слова полужирные */
    .R{color:gray} /*Серые регвыражения */


    upd: учтя исправления и дополнения, получил скрипт сам себя подсвечивающий, а вот версия от etcdema: dema.ru/syntax
    Поделиться публикацией
    Ммм, длинные выходные!
    Самое время просмотреть заказы на Фрилансим.
    Мне повезёт!
    Реклама
    Комментарии 30
    • 0
      Идея интересная, думаю вполне подойдет для личных блогов и не сильно специализированных на программировании сайтов. Большой плюс, что обработка производится на клиенте и не загружает сервер, а хороший синтаксический анализ все таки вещь не сильно быстрая. К сожалению закончились разряды, как будут обязательно плюсану.
      • 0
        Спасибо.
        • 0
          В JavaScript не силен, а подобная, не тяжелая подсветка как раз понадобилась, так что большое Вам спасибо.
          • +3
            Здорово. Коротко и быстро. И красиво.
            P.S. Сам пользуюсь hightlight.js, это пример «многих_многих_байт». Он крут, но и размерами гораздо больше, и грузится соответственно дольше.
            • +1
              Да, хорошая библиотека, тож её использовал использовал в нескольких проектах. Но вот захотелось чего-то небольшого и понятного.
              • НЛО прилетело и опубликовало эту надпись здесь
              • +1
                Вот наткнулся на не большую библиотечку codepress.org/ только там куча подсветок, автозакрытие скобок, автоподстоновки и все это чюдо весит не так уж и много 4к библиотека плюс по выбору правила с css для определённого языка примерно 2к
                • 0
                  Сайт лежит. (хабраэффект?) За ссылку спасибо, глянем как сайт встанет.
                  • 0
                    да заметил решил зайти посмотреть свежую версию и не смог :(, а библиотка хорошая можно в одминке использовать для редактирования исходников :)
                • 0
                  LISP подстветите мне так? :)
                  • +3
                    Насколько я знаю LISP, там подсвечивать особо нечего :)

                    code = code
                    .replace(/(defun|let|lambda|if|null|list|cons|append|loop)([^a-z0-9\$_])/gi,'$1$2')
                    .replace(/(\(|\)|\]|\[|\|)/gi,'$1')
                    .replace(/(\;[^\n\r]*(\n|\r\n))/g,'$1')
                    .replace(/(".*?")/g,'$1')
                    .replace(/\(([a-z\_\$][a-z0-9_]*)/gi,'($1')
                    .replace(/\t/g,' ');

                    как-то так :)
                    • 0
                      не проверял, но вроде должно быть правильно
                  • 0
                    Может кто подскажет хорошую библу для подсветки css/sql/xml/js?
                    Желательно на php
                    • +3
                      Немного исправленный вариант:
                      function Syntax(code)
                      {
                      	var comments	= [];	// Тут собираем все каменты
                      	var strings		= [];	// Тут собираем все строки
                      	var res			= [];	// Тут собираем все RegExp
                      	var all			= { 'C': comments, 'S': strings, 'R': res };
                      	var safe		= { '<': '<', '>': '>', '&': '&' };
                      
                      	return code
                      	// Маскируем HTML
                      						.replace(/[<>&]/g, function (m)
                      							{ return safe[m]; })
                      	// Убираем каменты
                      						.replace(/\/\*[\s\S]*\*\//g, function(m)
                      							{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~';   })
                      						.replace(/([^\\])\/\/[^\n]*\n/g, function(m, f)
                      							{ var l=comments.length; comments.push(m); return f+'~~~C'+l+'~~~'; })
                      	// Убираем regexp
                      						.replace(/\/(\\\/|[^\/\n])*\/[gim]{0,3}/g, function(m)
                      							{ var l=res.length; res.push(m); return '~~~R'+l+'~~~';   })
                      	// Убираем строки
                      						.replace(/([^\\])((?:'(?:\\'|[^'])*')|(?:"(?:\\"|[^"])*"))/g, function(m, f, s)
                      							{ var l=strings.length; strings.push(s); return f+'~~~S'+l+'~~~'; })
                      	// Выделяем ключевые слова
                      						.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
                      							'<span class="kwrd">$1</span>$2')
                      	// Выделяем скобки
                      						.replace(/(\{|\}|\]|\[|\|)/gi,
                      							'<span class="gly">$1</span>')
                      	// Выделяем имена функций
                      						.replace(/([a-z\_\$][a-z0-9_]*)[\s]*\(/gi,
                      							'<span class="func">$1</span>(')
                      	// Возвращаем на место каменты, строки, RegExp
                      						.replace(/~~~([CSR])(\d+)~~~/g, function(m, t, i)
                      							{ return '<span class="'+t+'">'+all[t][i]+'</span>'; })
                      	// Выставляем переводы строк
                      						.replace(/\n/g,
                      							'
                      ')
                      	// Табуляцию заменяем неразрывными пробелами
                      						.replace(/\t/g,
                      							'    ');
                      }
                      

                      • +1
                        Спасибо, добавил в пост.
                        • +1
                          Еще чуть-чуть поправлю: Стили
                          .S span{color:red;font-weight:normal}/* Всё внутри строки — строка */
                          .C span{color:orange;font-weight:normal}/* Всё внутри комментария — комментарий */
                          

                          ненужны, т. к. в моем варианте в каменты и строки ничего вложено не будет.
                          • 0
                            ахда, точно (рефлекс копипасты сработал)
                          • +1
                            Немного неправильные 2 последние замены (сразу не обратил внимание), должно быть так:
                            // Выставляем переводы строк
                            	.replace(/\n/g,	'<'+'br>')
                            // Табуляцию заменяем неразрывными пробелами
                            	.replace(/\t/g, '&'+'nbsp;&'+'nbsp;&'+'nbsp;&'+'nbsp;');
                            


                            • +1
                              ага. хабр съел пару тэгов.
                              всё поправлено
                        • 0
                          Спасибо.
                          Хоть и повторюсь, но здорово. Коротко и быстро. И красиво.
                          • 0
                            Позволю себе добавить следующее:

                            Код, скопированный из редактора с принудительной заменой табуляции пробелами (что есть правильно на мой взгляд) будет выглядеть абсолютно некошерно.

                            Это можно исправить, объявив локальную переменную:
                            var spacesInTab = 4;


                            И дописать простенький регэксп:
                            .replace(new RegExp('\\s{' + spacesInTab + '}', 'g'),
                                new Array(spacesInTab + 1).join("&nbsp;"));
                            


                            Вуаля.
                            • +1
                              Насчет
                              Если кто может разместить сей файлик на постоянной основе — будьте так любезны, а ссылку я выложу.

                              Немного посидел, довел до ума и положил тут: http://dema.ru/syntax/ неминимизированный код с комментариями занимает 192 строки или 7 202 байт, если выкинуть все лишнее — примерно 2Кб.
                              • 0
                                Пару раз заметил глюк: вначале и в конце строки комментарий считается пустым регулярным выражением. И ещё у вас на сайте (не сочтите за дерзость) «плагны» написано, вместо «Плагины».
                            • 0
                              Неплохо было бы еще избавиться от необходимости прописывать стили в документе, а тем более подключать внешний файл. Например, так:

                              $.fn.syntax = function(){

                              // add style
                              var style = $('<style>.S{color:red;}.func{color:blue;}.C{color:orange;}.kwrd{font-weight:bold;}.R{color:gray;}</style> ');
                              $("body").append(style);
                              return this.each(function(){
                              ....
                              ....


                              Только нужно будет дать стилям уникальные имена, напр. syntaxjs_func, syntaxjs_C и т. д., чтобы не пересекались с другими.
                              Также, цвета можно вынести в настройки.
                              • 0
                                Всё гениальное — просто. Лаконичная и красивая реализация!
                                • 0
                                  Спасибо.

                                  Могу я считать, что этот код выпущен под свободной лицензией? ))
                                  Сделаем расширение для PunBB 1.3.
                                  • 0
                                    Пожалуйста.

                                    Код, изначально написанный мной, может считаться выпущенным под свободной лицензией. По поводу же кода хабраюзера ETCDema ничего сказать не могу. Строго говоря, нельзя считать его код модификацией моего, поэтому лучше у него спросить :)
                                  • 0
                                    Сильно развил вашу идею и написал достаточно хорошую подсветку под систему ucoz. Можно конечно использовать и на сторонних ресурсах. Прошу оценить подсветку прямо на этой странице. Подсвечиваются js, css, html. Весь код укладывается в 9 килобайт.
                                    • –1
                                      Это получается не javascript, а jQuery.

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.