шаблоны OK для начала, но, как правило, это проще переопределять все более непосредственно, как только вы знаете, в каком направлении вы собираетесь в
Вот как все части подходят друг к другу:.
- Классификатор (на самом деле, теггер
IClassificationTag
) дает классификационные тэги для определенного раздела текстового буфера по требованию.
- Классификация тегов состоит из диапазона в буфере, к которому применяется тег, и самого классификационного тега. В теге классификации просто указывается тип классификации.
- Типы классификации используются для связывания тегов этой классификации с заданным форматом.
- Форматы (в частности,
ClassificationFormatDefinition
s) экспортируются через MEF (как EditorFormatDefinition
s), чтобы VS мог их обнаружить и использовать для цветных пространств, имеющих соответствующий тип классификации. Они также (необязательно) отображаются в шрифтах & Варианты цветов.
- Поставщик классификатора экспортируется через MEF для того, чтобы VS мог его обнаружить; он дает VS средство создания вашего классификатора для каждого открытого буфера (и, таким образом, обнаруживает в нем теги).
Итак, что вам нужно, это код, который определяет и экспортирует два определения формата классификации, связанные с двумя типами классификации, соответственно. Тогда ваш классификатор должен соответствующим образом создавать теги обоих типов. Вот пример (непроверенные):
public static class Classifications
{
// These are the strings that will be used to form the classification types
// and bind those types to formats
public const string ArchiveKey = "MyProject/ArchiveKey";
public const string ArchiveKeyVar = "MyProject/ArchiveKeyVar";
// These MEF exports define the types themselves
[Export]
[Name(ArchiveKey)]
private static ClassificationTypeDefinition ArchiveKeyType = null;
[Export]
[Name(ArchiveKeyVar)]
private static ClassificationTypeDefinition ArchiveKeyVarType = null;
// These are the format definitions that specify how things will look
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKey)]
[UserVisible(true)] // Controls whether it appears in Fonts & Colors options for user configuration
[Name(ArchiveKey)] // This could be anything but I like to reuse the classification type name
[Order(After = Priority.Default, Before = Priority.High)] // Optionally include this attribute if your classification should
// take precedence over some of the builtin ones like keywords
public sealed class ArchiveKeyFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xFF, 0x69, 0xB4); // pink!
DisplayName = "This will display in Fonts & Colors";
}
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ArchiveKeyVar)]
[UserVisible(true)]
[Name(ArchiveKeyVar)]
[Order(After = Priority.Default, Before = Priority.High)]
public sealed class ArchiveKeyVarFormatDefinition : ClassificationFormatDefinition
{
public ArchiveKeyVarFormatDefinition()
{
ForegroundColor = Color.FromRgb(0xB0, 0x30, 0x60); // maroon
DisplayName = "This too will display in Fonts & Colors";
}
}
}
Поставщик:
[Export(typeof(ITaggerProvider))]
[ContentType("text")] // or whatever content type your tagger applies to
[TagType(typeof(ClassificationTag))]
public class ArchiveKeyClassifierProvider : ITaggerProvider
{
[Import]
public IClassificationTypeRegistryService ClassificationTypeRegistry { get; set; }
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
return buffer.Properties.GetOrCreateSingletonProperty(() =>
new ArchiveKeyClassifier(buffer, ClassificationTypeRegistry)) as ITagger<T>;
}
}
Наконец, сама Таггер:
public class ArchiveKeyClassifier : ITagger<ClassificationTag>
{
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private Dictionary<string, ClassificationTag> _tags;
public ArchiveKeyClassifier(ITextBuffer subjectBuffer, IClassificationTypeRegistryService classificationRegistry)
{
// Build the tags that correspond to each of the possible classifications
_tags = new Dictionary<string, ClassificationTag> {
{ Classifications.ArchiveKey, BuildTag(classificationRegistry, Classifications.ArchiveKey) },
{ Classifications.ArchiveKeyVar, BuildTag(classificationRegistry, Classifications.ArchiveKeyVar) }
};
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans.Count == 0)
yield break;
foreach (var span in spans) {
if (span.IsEmpty)
continue;
foreach (var identSpan in LexIdentifiers(span)) {
var ident = identSpan.GetText();
if (!ident.StartsWith("Archive") || !ident.EndsWith("Key"))
continue;
var varSpan = new SnapshotSpan(
identSpan.Start + "Archive".Length,
identSpan.End - "Key".Length);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(identSpan.Start, varSpan.Start), _tags[Classifications.ArchiveKey]);
yield return new TagSpan<ClassificationTag>(varSpan, _tags[Classifications.ArchiveKeyVar]);
yield return new TagSpan<ClassificationTag>(new SnapshotSpan(varSpan.End, identSpan.End), _tags[Classifications.ArchiveKey]);
}
}
}
private static IEnumerable<SnapshotSpan> LexIdentifiers(SnapshotSpan span)
{
// Tokenize the string into identifiers and numbers, returning only the identifiers
var s = span.GetText();
for (int i = 0; i < s.Length;) {
if (char.IsLetter(s[i])) {
var start = i;
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
yield return new SnapshotSpan(span.Start + start, i - start);
continue;
}
if (char.IsDigit(s[i])) {
for (++i; i < s.Length && IsTokenChar(s[i]); ++i);
continue;
}
++i;
}
}
private static bool IsTokenChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_';
}
private static ClassificationTag BuildTag(IClassificationTypeRegistryService classificationRegistry, string typeName)
{
return new ClassificationTag(classificationRegistry.GetClassificationType(typeName));
}
}
Еще одно замечание: Для ускорения запуска, VS держит кеш экспорта MEF. Однако этот кеш часто не является недействительным, когда он должен быть. Кроме того, если вы измените цвет по умолчанию существующего определения формата классификации, есть хорошая вероятность, что ваши изменения не будут получены, потому что VS сохраняет предыдущие значения в реестре. Чтобы смягчить это, лучше всего запускать пакетный скрипт между компиляторами, когда все изменения, связанные с MEF- или форматированием, становятся ясными.Вот пример для VS2013 и корня суффикса Exp (используется по умолчанию при тестировании VSIXes):
@echo off
del "%LOCALAPPDATA%\Microsoft\VisualStudio\12.0Exp\ComponentModelCache\Microsoft.VisualStudio.Default.cache" 2> nul
rmdir /S /Q "%LOCALAPPDATA%\Microsoft\VisualStudio\12.0Exp\ComponentModelCache" 2> nul
reg delete HKCU\Software\Microsoft\VisualStudio\12.0Exp\FontAndColors\Cache\{75A05685-00A8-4DED-BAE5-E7A50BFA929A} /f
кроме обоих типов ClassificationFormatDefinition, используя тот же [Имя (ArchiveKey)] связывают это работает. Я определенно могу основываться на этом. –
Упс, хороший улов. Редактирование. Рад, что вы сочтете это полезным! – Cameron
@Cameron: В вашем ответе вы говорите: «Есть хороший шанс, что ваши изменения не будут получены, потому что VS сохраняет прежние значения в реестре. Чтобы смягчить это, лучше всего запустить пакетный скрипт между компиляторами, когда что-то MEF- или изменения, связанные с форматом, чтобы очистить вещи ». Вы знаете способ аннулирования этих кешированных значений из внутреннего расширения? – HJLebbink