Как сохранить оригинальную расцветку вашего кода из Visual Studio в публикации на Хабрахабре. Сравниваем расцветки

в 7:29, , рубрики: ReSharper, source code, Visual Studio, хабрахабр

Привет, читатель! В этом посте я расскажу, как можно сделать свой код на Хабре более «живым» благодаря простому способу сохранения его оригинальной цветовой схемы. А также предлагаю сравнить различные варианты расцветки кода.

Предисловие

Сейчас я нахожусь в процессе написания своей первой статьи для Хабра про веб-сервисы. И с самого начала решил, что исходные коды буду подсвечивать сторонним хабра-хайлайтером, так как стандартный тег <source> по моему мнению не дает качественного эффекта подсветки синтаксиса. Однако я обнаружил только один альтернативный хайлайтер — http://highlight.hohli.com. Результат его расцветки меня, к сожалению, не воодушевил. В конце поста я его показываю в числе 6 вариантов расцветки для непосредственного сравнения. Когда-то раньше был еще один хайлатер — Source Code Highlighter, описанный на Хабре тут и тут, но он уже давно не работает. Тут я вспомнил, что код из Visual Studio полностью сохраняет свой вид при копировании в Word, этот нехитрый прием и стал основой моего метода.

Метод

Идея проста: код копируется в Word, документ сохранятся как html веб-страница и функция преобразует html код в хабра-совместимый. Рассмотрим на примере. Этот простой код при сохранении дает следующее содержимое <body>, где синим цветом я обозначил все теги <span>, розовым – цвет стиля, а коричневым – сам отображаемый текст.

private void Hello(){
    MessageBox.Show("Hello Habrahabr!");
}

Hello.htm

<body lang=RU style='tab-interval:35.4pt'>
<div class=WordSection1>
 
<p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height:
normal;mso-layout-grid-align:none;text-autospace:none'><span lang=EN-US
style='font-size:9.5pt;font-family:Consolas;color:black;background:white;
mso-highlight:white;mso-ansi-language:EN-US'><span
style='mso-spacerun:yes'>        </span></span><span class=GramE><span
lang=EN-US style='font-size:9.5pt;font-family:Consolas;color:blue;background:
white;mso-highlight:white;mso-ansi-language:EN-US'>private</span></span><span
lang=EN-US style='font-size:9.5pt;font-family:Consolas;color:black;background:
white;mso-highlight:white;mso-ansi-language:EN-US'> </span><span lang=EN-US
style='font-size:9.5pt;font-family:Consolas;color:blue;background:white;
mso-highlight:white;mso-ansi-language:EN-US'>void</span><span lang=EN-US
style='font-size:9.5pt;font-family:Consolas;color:black;background:white;
mso-highlight:white;mso-ansi-language:EN-US'> Hello(){<o:p></o:p></span></p>
 
<p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height:
normal;mso-layout-grid-align:none;text-autospace:none'><span lang=EN-US
style='font-size:9.5pt;font-family:Consolas;color:black;background:white;
mso-highlight:white;mso-ansi-language:EN-US'><span
style='mso-spacerun:yes'>            </span></span><span class=GramE><span
lang=EN-US style='font-size:9.5pt;font-family:Consolas;color:#2B91AF;
background:white;mso-highlight:white;mso-ansi-language:EN-US'>MessageBox</span><span
lang=EN-US style='font-size:9.5pt;font-family:Consolas;color:black;background:
white;mso-highlight:white;mso-ansi-language:EN-US'>.Show(</span></span><span
lang=EN-US style='font-size:9.5pt;font-family:Consolas;color:#A31515;
background:white;mso-highlight:white;mso-ansi-language:EN-US'>&quot;Hello
Habrahabr!&quot;</span><span lang=EN-US style='font-size:9.5pt;font-family:
Consolas;color:black;background:white;mso-highlight:white;mso-ansi-language:
EN-US'>);<o:p></o:p></span></p>
 
<p class=MsoNormal><span lang=EN-US style='font-size:9.5pt;line-height:115%;
font-family:Consolas;color:black;background:white;mso-highlight:white;
mso-ansi-language:EN-US'><span style='mso-spacerun:yes'>        </span></span><span
style='font-size:9.5pt;line-height:115%;font-family:Consolas;color:black;
background:white;mso-highlight:white'>}</span></p>
 
</div>
</body>

Как видим, каждая строка исходного кода преобразуется в тег <p>, внутри которого с помощью вложенных тегов <span> производится форматирование текста, а его цвет хранится в атрибуте style. Становится ясен алгоритм преобразования: для каждого тега <p> из "/html/body/div/p" произвести рекурсивную трансляцию его содержимого в новый html документ, попутно с помощью тега <font> устанавливая цвет текста. Под спойлером приведен полный исходный код получившегося метода, который выглядит в соответствии с моей оригинальной цветовой схемой. Для работы с html использована библиотека Html Agility Pack.

HabraCodeFormatter.cs

  1. using System;
  2. using System.IO;
  3. using HtmlAgilityPack;
  4.  
  5. namespace HabraCode{
  6.     public static class HabraCodeFormatter{
  7.         const char spaceClassic = (char)32;
  8.         const char spaceNbsp = (char)160;
  9.  
  10.         public static void Format(string fileHtm, bool withLineNumbers = false){
  11.             var info = new FileInfo(fileHtm);
  12.             string destFile = info.DirectoryName + "\" + info.Name.Replace(info.Extensionnull) + "_result.txt";
  13.             Format(fileHtm, destFile, withLineNumbers);
  14.         }
  15.  
  16.  
  17.         public static void Format(string fileHtm, string fileDest, bool withLineNumbers = false){
  18.             var doc = new HtmlDocument();
  19.             doc.Load(fileHtm);
  20.             var nodes = doc.DocumentNode.SelectNodes("/html/body/div/p");
  21.             int spacesToDelete = getSpacesToDelete(nodes[0]);
  22.  
  23.             var result = new HtmlDocument();
  24.             var rootNode = result.CreateElement("blockquote");
  25.             result.DocumentNode.AppendChild(rootNode);
  26.  
  27.             if (withLineNumbers){
  28.                 var nodeOl = result.CreateElement("ol");
  29.                 rootNode.AppendChild(nodeOl);
  30.                 rootNode = nodeOl;
  31.             }
  32.  
  33.             foreach (var node in nodes){
  34.                 if (withLineNumbers){
  35.                     var nodeLi = doc.CreateElement("li");
  36.                     nodeLi.AppendChildren(node.ChildNodes);
  37.                     node.RemoveAllChildren();
  38.                     node.AppendChild(nodeLi);
  39.                 }
  40.  
  41.                 int refSpaces = spacesToDelete;
  42.                 translateNode(node, rootNode, ref refSpaces);
  43.                 rootNode.AppendChild(result.CreateTextNode(Environment.NewLine));
  44.             }
  45.  
  46.             result.Save(fileDest);
  47.         }
  48.  
  49.  
  50.         private static int getSpacesToDelete(HtmlNode node){
  51.             for (int index = 0; index < node.InnerText.Length; index++){
  52.                 if (!isSpace(node.InnerText[index])){
  53.                     return index;
  54.                 }
  55.             }
  56.  
  57.             return 0;
  58.         }
  59.  
  60.  
  61.         private static void translateNode(HtmlNode node, HtmlNode destParent, ref int spacesToDelete){
  62.             HtmlNode destNode = destParent;
  63.  
  64.             switch (node.Name){
  65.                 case "o:p":
  66.                 case "p":
  67.                     break;
  68.  
  69.                 case "span":
  70.                     string color = getColor(node);
  71.  
  72.                     if (color != null){
  73.                         destNode = destParent.OwnerDocument.CreateElement("font");
  74.                         destNode.SetAttributeValue("color", color);
  75.                     }
  76.                     break;
  77.  
  78.                 case "#text":
  79.                     string text = translateText(node.InnerTextref spacesToDelete);
  80.                     destNode = destParent.OwnerDocument.CreateTextNode(text);
  81.                     break;
  82.  
  83.                 case "b":
  84.                 case "li":
  85.                     destNode = destParent.OwnerDocument.CreateElement(node.Name);
  86.                     break;
  87.  
  88.                 default:
  89.                     throw new InvalidOperationException("Unexpected node.Name " + node.Name);
  90.             }
  91.  
  92.             if (!ReferenceEquals(destNode, destParent)){
  93.                 destParent.ChildNodes.Add(destNode);
  94.             }
  95.  
  96.             if (node.HasChildNodes){
  97.                 foreach (HtmlNode child in node.ChildNodes){
  98.                     translateNode(child, destNode, ref spacesToDelete);
  99.                 }
  100.             }
  101.         }
  102.  
  103.  
  104.         private static string translateText(string text, ref int spaces){
  105.             if (string.IsNullOrEmpty(text)){
  106.                 return text;
  107.             }
  108.  
  109.             text = text.Replace("http://""http://");
  110.             text = text.Replace("rn"" ");
  111.  
  112.             if (spaces > 0 && text.Length >= spaces && isSpace(text.Substring(0, spaces))){
  113.                 text = text.Remove(0, spaces);
  114.                 spaces = 0;
  115.             }
  116.  
  117.             text = text.Replace(spaceClassic.ToString(), "&nbsp;");
  118.             text = text.Replace(spaceNbsp.ToString(), "&nbsp;");
  119.             return text;
  120.         }
  121.  
  122.  
  123.         private static bool isSpace(char ch){
  124.             return (ch == spaceClassic || ch == spaceNbsp);
  125.         }
  126.  
  127.  
  128.         private static bool isSpace(string text){
  129.             foreach (char ch in text){
  130.                 if (!isSpace(ch)){
  131.                     return false;
  132.                 }
  133.             }
  134.  
  135.             return true;
  136.         }
  137.  
  138.  
  139.         private static string getColor(HtmlNode node){
  140.             var attr = node.Attributes["style"];
  141.  
  142.             if (attr == null || attr.Value == null){
  143.                 return null;
  144.             }
  145.  
  146.             string[] values = attr.Value.Split(new[] { ';' });
  147.  
  148.             foreach (string value in values){
  149.                 const string prefixColor = "color:";
  150.                 string trimmed = value.Trim();
  151.  
  152.                 if (trimmed.StartsWith(prefixColor)){
  153.                     return trimmed.Remove(0prefixColor.Length);
  154.                 }
  155.             }
  156.  
  157.             return null;
  158.         }
  159.     }
  160. }

Вероятно, многие назовут мою расцветку кода слишком пестрой, и потому неудобной, однако она ощутимо улучшает мой процесс работы с исходным кодом. Это очень простой способ немного повысить свою эффективность. Немного прокомментирую код. Класс содержит две перегрузки метода Format, где обязательным параметром является путь до исходного html файла, а необязательным – флаг добавления номеров строк. Вторая перегрузка позволяет явно указать конечный файл, иначе результат будет сохранен как [исходное_имя]_result.txt. Метод getSpacesToDelete позволяет найти количество начальных пробелов в первой строке, чтобы удалить их во всех последующих строках.

Настройка шрифта и расцветки исходного кода в Visual Studio производится в меню Tools > Options > Environment > Fonts and Colors. Однако изначально в нем вообще отсутствует возможность установить свой цвет для метода или свойства. Но если вы пользуетесь ReSharper’ом, такая возможность появляется. Для этого в меню ReSharper > Options > Code Inspection > Settings необходимо включить пункт Color identifires. И вот тут можно испытать легкий когнитивный диссонанс, потому что методы станут бирюзового цвета – такой цвет имеют классы в традиционной схеме, а классы станут темно-синими — все это можно увидеть ниже, где я сравниваю расцветки.

В моей схеме классы, ключевые слова и другие элементы имеют классический цвет, но к ним добавляются свои цвета для методов и свойств. Для тех, кто хочет ее попробовать, привожу настройку. Сохраните xml под спойлером как файл с расширением .vssettings в кодировке UTF-8 без BOM, например, HabraCode.vssettings, и сделайте импорт настройки через меню Tools > Import and Export Settings > Import... Кодировку можно задать, например, с помощью Notepad++. По умолчанию мастер также попутно сохраняет все ваши текущие настройки – для возможности отката изменений. После импорта шрифт будет сброшен на дефолтный, и если у вас был кастомный, его надо будет указать заново. Также в настройке «Plain Text» я слегка снизил белизну фону, уменьшив с абсолютной до #FAFAFA.

HabraCode.vssettings

<UserSettings>
    <ApplicationIdentity version="10.0"/>
    <ToolsOptions>
        <ToolsOptionsCategory name="Environment" RegisteredName="Environment"/>
    </ToolsOptions>
    <Category name="Environment_Group" RegisteredName="Environment_Group">
        <Category name="Environment_FontsAndColors" Category="{1EDA5DD4-927A-43a7-810E-7FD247D0DA1D}" Package="{DA9FB551-C724-11d0-AE1F-00A0C90FFFC3}" RegisteredName="Environment_FontsAndColors" PackageName="Visual Studio Environment Package">
            <PropertyValue name="Version">2</PropertyValue>
            <FontsAndColors Version="2.0">
                <Categories>					
                    <Category GUID="{A27B4E24-A735-4D1D-B8E7-9716E1E3D8E0}">
                        <Items>
<!--Черный цвет-->
<Item Name="Plain Text" Foreground="0x02000000" Background="0x00FAFAFA" BoldFont="No"/>
<Item Name="Literal" Foreground="0x02000000" Background="0x02000000" BoldFont="Yes"/>
<Item Name="Number" Foreground="0x02000000" Background="0x02000000" BoldFont="Yes"/>
<Item Name="ReSharper Namespace Identifier" Foreground="0x02000000" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Mutable Local Variable Identifier" Foreground="0x02000000" Background="0x02000000" BoldFont="No"/>
<!--Строки: кориченвый -->
<Item Name="String" Foreground="0x001515A3" Background="0x02000000" BoldFont="No"/>
<Item Name="String(C# @ Verbatim)" Foreground="0x001515A3" Background="0x02000000" BoldFont="No"/>
<!--Методы: бледно-малиновый -->
<Item Name="ReSharper Extension Method Identifier" Foreground="0x008812C7" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Method Identifier" Foreground="0x008812C7" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Operator Identifier" Foreground="0x008812C7" Background="0x02000000" BoldFont="No"/>
<!--Свойства, поля, события: рыже-коричневый -->
<Item Name="ReSharper Event Identifier" Foreground="0x001256C7" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Field Identifier" Foreground="0x001256C7" Background="0x02000000" BoldFont="No"/>
<!--Типы: бирюзовый -->
<Item Name="ReSharper Class Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Delegate Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Enum Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Static Class Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Interface Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Struct Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<Item Name="ReSharper Type Parameter Identifier" Foreground="0x00AC8500" Background="0x02000000" BoldFont="No"/>
<!--Комментарии todo и bug. NotImplementedException: красный-->
<Item Name="ReSharper Todo Item" Foreground="0x000000F5" Background="0x02000000" BoldFont="Yes"/>
<Item Name="ReSharper Todo Item Marker on Error Stripe" Foreground="0x000000F5" Background="0x02000000" BoldFont="No"/>
<!--Элемент string.Format: бирюзовый + bold-->
<Item Name="ReSharper Format String Item" Foreground="0x00AC8500" Background="0x02000000" BoldFont="Yes"/>
<!--Константы: кориченвый + bold -->
<Item Name="ReSharper Constant Identifier" Foreground="0x00004080" Background="0x02000000" BoldFont="Yes"/>
                        </Items>
                    </Category>
                </Categories>
            </FontsAndColors>
        </Category>
    </Category>
</UserSettings>

Сравнение

Теперь я предлагаю сравнить 6 вариантов расцветки кода на примере одного из методов. Нельзя утверждать, что какая-то расцветка в принципе лучшая, потому что, как известно, на вкус и цвет товарищей нет. Но меня интересует коллективное мнение хабра-сообщества.

1. <source lang="cs">

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];

    if (attr == null || attr.Value == null){
        return null;
    }

    string[] values = attr.Value.Split(new [] {';'});

    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();

        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0, prefixColor.Length);
        }
    }

    return null;
}

2. Черно-белый код (получен через HabraCodeFormatter с отбросом цвета и bold’а)

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];
 
    if (attr == null || attr.Value == null){
        return null;
    }
 
    string[] values = attr.Value.Split(new [] {';'});
 
    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();
 
        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0, prefixColor.Length);
        }
    }
 
    return null;
}
 

3. http://highlight.hohli.com

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];
 
    if (attr == null || attr.Value == null){
        return null;
    }
 
    string[] values = attr.Value.Split(new [] {';'});
 
    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();
 
        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0, prefixColor.Length);
        }
    }
 
    return null;
}

4. Стандартная расцветка Visual Studio

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];
 
    if (attr == null || attr.Value == null){
        return null;
    }
 
    string[] values = attr.Value.Split(new [] {';'});
 
    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();
 
        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0, prefixColor.Length);
        }
    }
 
    return null;
}

5. Стандартная расцветка Reshaper

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];
 
    if (attr == null || attr.Value == null){
        return null;
    }
 
    string[] values = attr.Value.Split(new [] {';'});
 
    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();
 
        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0, prefixColor.Length);
        }
    }
 
    return null;
}
 

6. Авторская расцветка

private static string getColor(HtmlNode node){
    var attr = node.Attributes["style"];
 
    if (attr == null || attr.Value == null){
        return null;
    }
 
    string[] values = attr.Value.Split(new [] {';'});
 
    foreach (string value in values){
        const string prefixColor = "color:";
        string trimmed = value.Trim();
 
        if (trimmed.StartsWith(prefixColor)){
            return trimmed.Remove(0prefixColor.Length);
        }
    }
 
    return null;
}
 

Я бы хотел создать опрос, чтобы узнать, какие из этих вариантов расцветки кода устроили бы хабра-сообщество в публикациях, но у меня недостаточно привилегий.

Автор: capslocky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js