Как завершение кода работает?

Определенно! Мы используем комбинацию JUnit, задачи Ant для выполнения их, и , Гудзон для продолжает интеграционные тесты. Работы как очарование.

82
задан Michael J. Barber 1 April 2012 в 09:10
поделиться

2 ответа

Механизм IntelliSense в моем сервисном продукте языка UnrealScript сложен, но я дам здесь как можно лучший обзор. Служба языка C # в VS2008 SP1 - моя цель производительности (не зря). Его еще нет, но он достаточно быстрый / точный, чтобы я мог безопасно предлагать предложения после ввода одного символа, не дожидаясь нажатия Ctrl + пробел или ввода пользователем . (точка). Чем больше информации об этом получат люди [работающие над языковыми услугами], тем лучше будут мои впечатления от использования их продуктов. Есть ряд продуктов, которые я У меня был неудачный опыт работы с тем, что не уделял такого пристального внимания деталям, и в результате я боролся с IDE больше, чем кодировал.

В моей языковой службе это выглядит следующим образом:

  1. Получить выражение под курсором. Это идет от начала выражения доступа к члену до конца идентификатора, на котором находится курсор. Выражение доступа к членам обычно имеет вид aa.bb.cc , но может также содержать вызовы методов, как в aa.bb (3 + 2) .cc .
  2. Get контекст , окружающий курсор. Это очень сложно, потому что он не всегда следует тем же правилам, что и компилятор (длинная история), но здесь предположим, что это так. Обычно это означает получение кэшированной информации о методе / классе, внутри которого находится курсор.
  3. Допустим, объект контекста реализует IDeclarationProvider , где вы можете вызвать GetDeclarations () , чтобы получить IEnumerable всех элементов, видимых в области. В моем случае этот список содержит локальные переменные / параметры (если в методе), члены (поля и методы, только статические, если только в методе экземпляра, и нет закрытых членов базовых типов), глобальные переменные (типы и константы для языка I работаю над) и ключевыми словами. В этом списке будет элемент с именем aa . В качестве первого шага в оценке выражения в №1 мы выбираем элемент из контекстного перечисления с именем aa , что дает нам IDeclaration для следующего шага.
  4. Далее , Я применяю оператор к объявлению ID , представляющему aa , чтобы получить другой IEnumerable , содержащий «элементы» (в некотором смысле) aa . Начиная с . отличается от оператора -> , я вызываю объявление.GetMembers (".") и ожидаю, что объект IDeclaration правильно применит указанный оператор.
  5. Это продолжается до тех пор, пока я не нажму cc , где список объявлений может или не может содержать объект с именем cc . Как я уверен, вы знаете, если несколько элементов начинаются с cc , они тоже должны появиться. Я решаю эту проблему, взяв окончательное перечисление и пропустив его через мой документированный алгоритм , чтобы предоставить пользователю наиболее полезную возможную информацию.

Вот некоторые дополнительные примечания для серверной части IntelliSense:

  • Я делаю широкое использование механизмов отложенной оценки LINQ при реализации GetMembers . Каждый объект в моем кэше может предоставить функтор, который оценивает его члены, поэтому выполнение сложных действий с деревом почти тривиально.
  • Вместо того, чтобы каждый объект сохранял List своих членов , У меня есть список List , где Name - это структура, содержащая хеш-код специально отформатированной строки, описывающей член. Есть огромный кеш, который сопоставляет имена объектам. Таким образом, когда я повторно разбираю файл, Я могу удалить все элементы, объявленные в файле, из кеша и заново заполнить его обновленными членами. Из-за того, как сконфигурированы функторы, все выражения немедленно вычисляют новые элементы.

IntelliSense "frontend"

По мере того, как пользователь вводит, файл синтаксически неправильный чаще, чем правильный . Таким образом, я не хочу случайно удалять разделы кеша, когда пользователь вводит текст. У меня есть большое количество правил для особых случаев, чтобы как можно быстрее обрабатывать инкрементные обновления. Инкрементный кеш хранится только локально в открытом файле и помогает гарантировать, что пользователь не осознает, что их ввод заставляет внутренний кеш хранить неверную информацию о строках / столбцах для таких вещей, как каждый метод в файле.

  • Одно искупление фактор мой парсер быстрый . Он может обрабатывать полное обновление кэша исходного файла на 20000 строк за 150 мс, работая автономно в фоновом потоке с низким приоритетом. Каждый раз, когда этот синтаксический анализатор успешно завершает проход открытого файла (синтаксически), текущее состояние файла перемещается в глобальный кеш.
  • Если файл синтаксически неверен, я использую синтаксический анализатор фильтра ANTLR (извините о ссылке - большая часть информации находится в списке рассылки или собирается из чтения источника) для повторного анализа файла в поисках:
    • Объявления переменных / полей.
    • Подпись для определений классов / структур.
    • Подпись для определений методов.
  • В локальном кэше определения классов / структур / методов начинаются с подписи и заканчиваются, когда уровень вложенности скоб возвращается к четному. Методы также могут завершаться, если достигается объявление другого метода (нет методов вложения).
  • В локальном кэше переменные / поля связаны с непосредственно предшествующим незакрытым элементом. См. Краткий фрагмент кода ниже, чтобы понять, почему это важно.
  • Кроме того, по мере того, как пользователь вводит, я веду таблицу переназначения, в которой отмечаются добавленные / удаленные диапазоны символов. Это используется для:
    • Убедиться, что я могу определить правильный контекст курсора, поскольку метод может / действительно перемещается в файле между полными синтаксическими анализами.
    • Убедитесь, что Go To Declaration / Definition / Reference правильно находит элементы в открытых файлах.

Фрагмент кода для предыдущего раздела:

class A
{
    int x; // linked to A

    void foo() // linked to A
    {
        int local; // linked to foo()

    // foo() ends here because bar() is starting
    void bar() // linked to A
    {
        int local2; // linked to bar()
    }

    int y; // linked again to A

Я решил добавить список функций IntelliSense, которые я реализовал с помощью этого макета. Фотографии каждого из них находятся здесь.

  • Автозаполнение
  • Подсказки по инструментам
  • Подсказки по методам
  • Представление классов
  • Окно определения кода
  • Браузер вызовов (VS 2010 наконец-то добавляет это в C # )
  • Семантически правильный Найти все ссылки
63
ответ дан 24 November 2019 в 09:20
поделиться

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

Когда вы печатаете персонаж, он идет по тропинке в дереве. Все потомки конкретного trie-узла являются возможными дополнениями. Затем IDE просто нужно отфильтровать их по тем, которые имеют смысл в текущем контексте, но ему нужно вычислить только столько, сколько может быть отображено во всплывающем окне завершения табуляции.

Более продвинутое завершение табуляции требует более сложного дерева. Например, Visual Assist X имеет функцию, с помощью которой вам нужно вводить только заглавные буквы символов CamelCase - например, если вы вводите SFN, он показывает вам символ SomeFunctionName в своем окно завершения табуляции.

Вычисление дерева (или других структур данных) требует синтаксического анализа всего вашего кода, чтобы получить список всех символов в вашем проекте. Visual Studio хранит это в своей базе данных IntelliSense, файле .ncb , который хранится вместе с вашим проектом, поэтому ему не нужно заново анализировать все каждый раз, когда вы закрываете и снова открываете свой проект. Когда вы впервые открываете большой проект (скажем, тот, который вы только что синхронизировали с системой управления версиями), VS потребуется время, чтобы проанализировать все и сгенерировать базу данных.

Я не знаю, как он обрабатывает инкрементные изменения. Как вы сказали, когда вы пишете код, это неверный синтаксис в 90% случаев, и повторный анализ всего, когда вы простаиваете, приведет к огромному налогу на ваш процессор с очень небольшой выгодой, особенно если вы изменяете файл заголовка, включенный большое количество исходных файлов.

Я подозреваю, что он либо (а) повторно анализирует, когда вы действительно создаете свой проект (или, возможно, когда вы его закрываете / открываете), либо (б) он выполняет какой-то локальный анализ там, где он анализирует только код в том месте, где вы только что отредактировали, в некоторой ограниченной степени, просто чтобы получить имена соответствующих символов. Поскольку C ++ обладает такой чрезвычайно сложной грамматикой, он может вести себя странно в темных углах, если вы:

15
ответ дан 24 November 2019 в 09:20
поделиться
Другие вопросы по тегам:

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