Как работает завершение кода?
многие редакторы и IDEs имеют завершение кода. Некоторые из них очень "умны", другие-нет. Меня интересует более интеллигентный тип. Например, я видел IDEs, которые предлагают только функцию, если она a) доступна в текущей области b) ее возвращаемое значение допустимо. (Например, после "5 + foo[tab]" он предлагает только функции, которые возвращают что-то, что можно добавить в целочисленные или переменные имена правильного типа.) Я также видел, что они чаще всего используются или самый длинный вариант впереди списка.
Я понимаю, что вам нужно разобрать код. Но обычно при редактировании текущего кода недопустимы синтаксические ошибки в нем. Как вы разбираете что-то, когда оно неполное и содержит ошибки?
существует также ограничение по времени. Завершение бесполезно, если требуется несколько секунд, чтобы придумать список. Иногда алгоритм завершения имеет дело с тысячами классов.
какие хорошие алгоритмы и данные структуры для этого?
3 ответов
механизм IntelliSense в моем продукте службы языка UnrealScript сложен, но я дам как можно лучший обзор здесь. Языковая служба C# в VS2008 SP1 - это моя цель производительности (по уважительной причине). Это еще не там, но это быстро / достаточно точно, что я могу безопасно предлагать предложения после ввода одного символа, не дожидаясь ctrl + space или ввода пользователем .
(точка). Чем больше информации люди [работающие в языковых службах] получают по этому вопросу, лучший опыт конечного пользователя, который я получаю, должен ли я когда-либо использовать свои продукты. Есть ряд продуктов, с которыми у меня был неудачный опыт работы, которые не обращали такого пристального внимания на детали, и в результате я боролся с IDE больше, чем с кодированием.
в моей языковой службе это изложено следующим образом:
- получить выражение на курсоре. Это идет с самого начала выражение доступа к члену к конец идентификатора курсор закончен. Выражение доступа к элементу обычно имеет вид
aa.bb.cc
, но также может содержать вызовы методов, как вaa.bb(3+2).cc
. - скачать контекст вокруг курсора. Это очень сложно, потому что он не всегда следует тем же правилам, что и компилятор (длинная история), но здесь предположим, что это так. Как правило, это означает получение кэшированной информации о методе / классе, в котором находится курсор.
- сказать объект контекста орудия
IDeclarationProvider
, где вы можете позвонитьGetDeclarations()
для полученияIEnumerable<IDeclaration>
из всех элементов, видимых в области. В моем случае этот список содержит локальные / параметры (если в методе), члены (поля и методы, статические только если в методе экземпляра и никаких частных членов базовых типов), глобалы (типы и константы для языка, над которым я работаю) и ключевые слова. В этом списке будет элемент с именемaa
. В качестве первого шага при оценке выражения в #1 мы выбираем элемент из перечисление контекста с именемaa
, иIDeclaration
для следующего шага. - далее я применяю оператор к
IDeclaration
представляютaa
получить ещеIEnumerable<IDeclaration>
содержащий "члены" (в некотором смысле)aa
. С.
оператор отличается от->
оператор, я вызываюdeclaration.GetMembers(".")
и ожидатьIDeclaration
объект для правильного применения указанного оператора. - это продолжается, пока я не нажму
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++ имеет такую невероятно сложную грамматику, она может вести себя странно в темных углах, Если вы используете метапрограммирование тяжелых шаблонов и тому подобное.
следующая ссылка поможет вам дальше..
Подсветка Синтаксиса:быстрое цветное текстовое поле для подсветки синтаксиса