Как использовать буфер проекции для поддержки встроенных языков в редакторе Visual Studio

В конце первого абзаца в этой ссылке говорится:

Функция выделения текста Visual Studio реализована с использованием буфера прогнозирования, чтобы скрыть свернутый текст, а редактор Visual Studio для страниц ASP.NET использует проекцию для поддержки встроенных языков, таких как Visual Basic и С#.

Я искал и искал, но не нашел каких-либо примеров или документации для достижения этого, кто-нибудь знает, как это делается? Я получил классификацию и создал проекционный буфер промежутков, которые я хочу классифицировать как код С#. Я задал тип контекста буферов как "CSharp", но пролеты никогда не классифицируются. Я также попытался основать свой тип контента от "проекции", но теперь это работает.

Ответ 1

Рабочий пример можно найти здесь, но в соответствии с комментариями предупреждения это непростая задача.

// Abandon all hope ye who enters here.
// https://twitter.com/Schabse/status/393092191356076032
// https://twitter.com/jasonmalinowski/status/393094145398407168

// Based on decompiled code from Microsoft.VisualStudio.Html.ContainedLanguage.Server
// Thanks to Jason Malinowski for helping me navigate this mess.
// All of this can go away when the Roslyn editor ships.

Ответ 2

Наконец-то мне удалось успешно внедрить проекционные буферы в окно инструмента и подключить их к языковым службам С#. Одно предостережение: этот подход работает только для Visual Studio с использованием Roslyn. Я опубликовал проект Github, который вы можете использовать, а также сопровождающий блог пост.

Ответ на ваш вопрос длинный и включает в себя так много движущихся частей, что он не поддается стилю StackOverflow Q & Очень хорошо. При этом я подытожу необходимые шаги и включу некоторые соответствующие коды.

В следующем примере создается буфер проекции файла, состоящего из первых 100 символов файла.

Сначала создаем IVsInvisibleEditor для заданного пути к файлу и создаем для него окно кода. Мы устанавливаем содержимое этого окна кода как IVsTextLines для IVsInvisibleEditor.

Затем мы устанавливаем пользовательскую роль "CustomProjectionRole" в текстовом буфере этого окна кода. Эта роль позволяет нам настраивать текстовый буфер через экспортированный MEF ITextViewModelProvider.

public IWpfTextViewHost CreateEditor(string filePath, int start = 0, int end = 100)
{
    //IVsInvisibleEditors are in-memory represenations of typical Visual Studio editors.
    //Language services, highlighting and error squiggles are hooked up to these editors
    //for us once we convert them to WpfTextViews. 
    var invisibleEditor = GetInvisibleEditor(filePath);

    var docDataPointer = IntPtr.Zero;
    Guid guidIVsTextLines = typeof(IVsTextLines).GUID;

    ErrorHandler.ThrowOnFailure(invisibleEditor.GetDocData(
        fEnsureWritable: 1
        , riid: ref guidIVsTextLines
        , ppDocData: out docDataPointer));

    IVsTextLines docData = (IVsTextLines)Marshal.GetObjectForIUnknown(docDataPointer);

    //Create a code window adapter
    var codeWindow = _editorAdapter.CreateVsCodeWindowAdapter(VisualStudioServices.OLEServiceProvider);
    ErrorHandler.ThrowOnFailure(codeWindow.SetBuffer(docData));

    //Get a text view for our editor which we will then use to get the WPF control for that editor.
    IVsTextView textView;
    ErrorHandler.ThrowOnFailure(codeWindow.GetPrimaryView(out textView));

    //We add our own role to this text view. Later this will allow us to selectively modify
    //this editor without getting in the way of Visual Studio normal editors.
    var roles = _editorFactoryService.DefaultRoles.Concat(new string[] { "CustomProjectionRole" });

    var vsTextBuffer = docData as IVsTextBuffer;
    var textBuffer = _editorAdapter.GetDataBuffer(vsTextBuffer);

    textBuffer.Properties.AddProperty("StartPosition", start);
    textBuffer.Properties.AddProperty("EndPosition", end);
    var guid = VSConstants.VsTextBufferUserDataGuid.VsTextViewRoles_guid;
    ((IVsUserData)codeWindow).SetData(ref guid, _editorFactoryService.CreateTextViewRoleSet(roles).ToString());

    _currentlyFocusedTextView = textView;
    var textViewHost = _editorAdapter.GetWpfTextViewHost(textView);
    return textViewHost;
}

Теперь мы создаем IVsTextViewModelProvider, который создает и возвращает ProjectionTextViewModel. Этот ProjectionTextViewModel сохраняет буфер проекции в своем Visual Buffer. Это означает, что при отображении этого буфера отображается буфер проецирования. Однако языковые службы буфера данных резервного копирования работают правильно.

[Export(typeof(ITextViewModelProvider)), ContentType("CSharp"), TextViewRole("CustomProjectionRole")]
internal class ProjectionTextViewModelProvider : ITextViewModelProvider
{
    public ITextViewModel CreateTextViewModel(ITextDataModel dataModel, ITextViewRoleSet roles)
    {
        //Create a projection buffer based on the specified start and end position.
        var projectionBuffer = CreateProjectionBuffer(dataModel);
        //Display this projection buffer in the visual buffer, while still maintaining
        //the full file buffer as the underlying data buffer.
        var textViewModel = new ProjectionTextViewModel(dataModel, projectionBuffer);
        return textViewModel;

    }

    public IProjectionBuffer CreateProjectionBuffer(ITextDataModel dataModel)
    {
        //retrieve start and end position that we saved in MyToolWindow.CreateEditor()
        var startPosition = (int)dataModel.DataBuffer.Properties.GetProperty("StartPosition");
        var endPosition = (int)dataModel.DataBuffer.Properties.GetProperty("EndPosition");
        var length = endPosition - startPosition;

        //Take a snapshot of the text within these indices.
        var textSnapshot = dataModel.DataBuffer.CurrentSnapshot;
        var trackingSpan = textSnapshot.CreateTrackingSpan(startPosition, length, SpanTrackingMode.EdgeExclusive);

        //Create the actual projection buffer
        var projectionBuffer = ProjectionBufferFactory.CreateProjectionBuffer(
            null
            , new List<object>() { trackingSpan }
            , ProjectionBufferOptions.None
            );
        return projectionBuffer;
    }


    [Import]
    public IProjectionBufferFactoryService ProjectionBufferFactory { get; set; }
}

Надеемся, что это приведет к тому, что будущие посетители отправятся на хорошее начало.

Ответ 3

Буферы проектирования в Visual Studio были созданы в первую очередь для обработки сценариев, в которых один языковой регион встроен в другой язык. Классическими примерами являются CSS и Javascript внутри HTML. Аналогично, С# или VB в ASP.NET или Razor. На самом деле редактор HTML обрабатывает многие языки, а его проекционная буферная архитектура довольно расширяема (я написал большую часть). Таким образом, вся функциональность внутри блока стиля обрабатывается редактором CSS, а редактор HTML не должен многое делать.

Буфер проецирования не так усложняется, когда вы получаете, как он работает. В представлении представлены буферы прогноза, образующие граф, и буфер верхнего уровня. Буфер прогноза не имеет собственного контента, он состоит из проекционных промежутков, которые, в свою очередь, представляют собой промежутки отслеживания (ITrackingSpan) или инертные регионы (строки).

Рассмотрим блок стиля внутри HTML. Во-первых, вам нужно создать проекционный буфер с типом контента "проекция" или другим типом контента, который получен из "проекции". Затем вы создаете буфер прогноза, который будет содержать CSS с типом контента "CSS". Файл, считанный с диска, находится в текстовом буфере с типом содержимого "HTMLX" (тип содержимого "HTML" зарезервирован для классического редактора Web Forms). Редактор HTML анализирует файл и извлекает содержимое блока стиля, а также встроенные стили в отдельную строку. Фрагменты встроенного стиля декорируются в классы, поэтому они выглядят хорошо сформированными в редакторе CSS.

Теперь построены проекционные отображения. Первый буфер проектирования CSS заполнен инертными строками (они представляют собой CSS, не видимые для пользователя, такие как декорации встроенных стилей), а также интервалы отслеживания, созданные из буфера диска (HTML), которые определяют области, видимые пользователю - в частности, содержимое стиля блок (и).

Затем создаются прогнозы для буфера представления (верхнего уровня). Эти прогнозы представляют собой список интервалов отслеживания, который представляет собой комбинацию интервалов отслеживания, созданных из буфера проекции редактора CSS (НЕ с буфера HTML-диска), и интервалов отслеживания, созданных с буфера HTML файла, которые представляют части HTML представления.

График выглядит примерно так:

  View Buffer [ContentType = "projection"]
    |      \
    |     CSS Projection [ContentType = CSS]
    |      /
  Disk Buffer [ContentType = HTMLX]

Редактирование HTML-частей буфера представления отражается в буфере диска, а службы языка HTML обеспечивают завершение, проверку синтаксиса и т.д. Редактирование в блоках стилей идет в буфер проекта CSS, а редактор CSS обеспечивает проверку завершения и синтаксиса. Они также попадают в буфер диска через второй уровень выступов.

Теперь пересылка команд на встроенный язык (например, вызов контекстного меню) и сохранение правильного отображения точки останова для Javascript или С# - это отдельный код. Проецирование помогает только с функциями, связанными с просмотром, цепочкой контроллеров и операциями отладчика необходимо обрабатывать отдельно. Командный контроллер HTML-редактора знает о встроенных языках и в зависимости от позиции каретки передает команды до соответствующей языковой службы.

Ответ 4

Комментарий, который вы оставили, кажется, говорит о набросках, но не для поддержки встроенных языков. Если вы пытаетесь скрыть текст, вы можете взглянуть на этот Код IntraText.

Если вы загрузите и установите его или отлаживаете, установив проект запуска на devenv, а команда args на /RootSuffix Exp, вы можете открыть текстовый файл и ввести #CCCCCC или любой другой гексабельный веб-цвет. Код рухнет и покажет вам простую графику.

Насколько я знаю о поддержке встроенных языков, вам нужно написать свой собственный языковой сервис. Я хотел бы иметь возможность поддерживать функции языковых служб intellisense. Однако кажется, что вы должны изобрести колесо, когда дело доходит до такого поведения в визуальной студии.