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

многие редакторы и IDEs имеют завершение кода. Некоторые из них очень "умны", другие-нет. Меня интересует более интеллигентный тип. Например, я видел IDEs, которые предлагают только функцию, если она a) доступна в текущей области b) ее возвращаемое значение допустимо. (Например, после "5 + foo[tab]" он предлагает только функции, которые возвращают что-то, что можно добавить в целочисленные или переменные имена правильного типа.) Я также видел, что они чаще всего используются или самый длинный вариант впереди списка.

Я понимаю, что вам нужно разобрать код. Но обычно при редактировании текущего кода недопустимы синтаксические ошибки в нем. Как вы разбираете что-то, когда оно неполное и содержит ошибки?

существует также ограничение по времени. Завершение бесполезно, если требуется несколько секунд, чтобы придумать список. Иногда алгоритм завершения имеет дело с тысячами классов.

какие хорошие алгоритмы и данные структуры для этого?

3 ответов


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

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

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

вот некоторые дополнительные примечания для бэкэнда IntelliSense:

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

IntelliSense "frontend"

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

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

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

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#)
  • семантически правильно найти все ссылки

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

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

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

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

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

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


следующая ссылка поможет вам дальше..

Подсветка Синтаксиса:быстрое цветное текстовое поле для подсветки синтаксиса