В 2023 году популярность генеративного искусственного интеллекта приведет к тому, что все больше и больше организаций будут внедрять кодирование с помощью искусственного интеллекта. Что немного отличается от GitHub Copilot, выпущенного в 2021 году, так это то, что завершение кода — это лишь один из многих сценариев. Большое количество компаний изучают такие сценарии, как генерация полного кода и его проверка на основе требований, а также внедряют генеративный искусственный интеллект для повышения эффективности разработки.
В этом контексте мы (сообщество разработчиков программного обеспечения с открытым исходным кодом Thinkworks) также открыли исходный код ряда вспомогательных инструментов искусственного интеллекта, чтобы помочь большему количеству организаций создавать своих собственных помощников по программированию с помощью искусственного интеллекта:
Потому что, когда мы разрабатывали AutoDev, различные модели с открытым исходным кодом постоянно развивались. В этом контексте его действия таковы:
Поэтому это руководство также сосредоточено на этих трех шагах. Кроме того, исходя из нашего опыта, пример стека технологий для этого руководства:
Поскольку наш опыт в области ИИ относительно ограничен, некоторые ошибки неизбежно будут. Поэтому мы также надеемся работать с большим количеством разработчиков для создания этого проекта с открытым исходным кодом.
В сочетании с частью отчета JetBrains «Экосистема разработчиков» за 2023 год, посвященной искусственному интеллекту, мы можем обобщить некоторые общие сценарии, отражающие области, в которых генеративный ИИ может сыграть роль в процессе разработки. Вот некоторые из основных сценариев:
Когда мы создавали AutoDev, мы также обнаружили такие сценарии, как создание SQL DDL, создание требований, TDD и т. д. так. Мы предоставляем возможность настраивать сценарии, чтобы разработчики могли настраивать свои собственные возможности ИИ. Подробности см.: https://ide.unitmesh.cc/customize.
В ежедневном программировании существует несколько разных сценариев с разными требованиями к скорости реакции ИИ (просто в качестве примера):
сцена | Скорость ответа | Сформулируйте требования к качеству | Ожидаемый размер | иллюстрировать |
---|---|---|---|---|
завершение кода | быстрый | середина | 1~6Б | Завершение кода — наиболее распространенный сценарий ежедневного кодирования, и скорость ответа имеет решающее значение. |
Генерация документов | середина | середина | 1 | Генерация документации требует полного понимания структуры кода, при этом скорость и качество не менее важны. |
проверка кода | быстрый | середина | 1 | Проверка кода требует высококачественных рекомендаций, но также должна быть максимально оперативной. |
Генерация модульных тестов | быстрый | середина | 6Б~ | Модульные тесты генерируют меньше контекста, а скорость реагирования и качество ИИ одинаково важны. |
рефакторинг кода | середина | высокий | 32Б~ | Рефакторинг кода может потребовать большего понимания контекста, а время отклика может быть умеренно замедлено. |
формирование спроса | середина | высокий | 32Б~ | Формирование спроса — это относительно сложный сценарий, и скорость ответа можно умеренно замедлить, чтобы обеспечить точность. |
Поиск и интерпретация кода естественного языка | Средне-низкий | высокий | 32Б~ | Поиск и интерпретация кода естественного языка — это относительно сложные сценарии, и скорость ответа может быть умеренно замедлена для обеспечения точности. |
PS: 32B здесь выражен только на порядок, потому что эффект будет лучше с моделью большего размера.
Поэтому мы резюмируем это как: одна большая, одна средняя, одна микро и три модели, обеспечивающие комплексное кодирование с помощью искусственного интеллекта:
Завершение кода ИИ может сочетать инструменты IDE для анализа контекста кода и правил языка программирования, а ИИ автоматически генерирует или предлагает фрагменты кода. В инструментах завершения кода, подобных GitHub Copilot, они обычно делятся на три режима подразделения:
Встроенное завершение (Inline)
Как и в режиме FIM (заполнение посередине), завершенное содержимое находится в текущей строке. Например: BlotPost blogpost = new
, завершение: BlogPost();
для достижения: BlogPost blogpost = new BlogPost();
Мы можем использовать Deepseek Coder в качестве примера, чтобы увидеть эффект в этом сценарии:
< |fim▁begin| > def quick_sort(arr):
if len(arr) < = 1:
return arr
pivot = arr[0]
left = []
right = []
< |fim▁hole| >
if arr[i] < pivot:
left.append(arr[i])
else:
right.append(arr[i])
return quick_sort(left) + [pivot] + quick_sort(right) < |fim▁end| >
Здесь нам нужно объединить код до и после курсора.
Внутриблочное завершение (InBlock)
Достигается посредством контекстного обучения (In-Context Learning), содержимое завершения находится в текущем функциональном блоке. Например, исходный код:
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
}
Завершенный код:
val blogPost = BlogPost (
title = blogDto.title,
content = blogDto.content,
author = blogDto.author
)
return blogRepository.save(blogPost)
ПослеБлок
Достигается посредством контекстного обучения (In-Context Learning), завершения после текущего функционального блока, например: завершение новой функции после текущего функционального блока. Например, исходный код:
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
// ...
}
Завершенный код:
fun updateBlog ( id : Long , blogDto : CreateBlogDto ): BlogPost {
// ...
}
fun deleteBlog ( id : Long ) {
// ...
}
Когда мы создаем соответствующую функцию завершения AI, нам также необходимо рассмотреть возможность применения ее к соответствующему набору данных шаблона, чтобы улучшить качество завершения и обеспечить лучший пользовательский опыт.
Некоторые связанные ресурсы для написания этой статьи:
Пояснения к коду призваны помочь разработчикам более эффективно управлять большими базами кода и понимать их. Эти помощники могут отвечать на вопросы по кодовой базе, предоставлять документацию, осуществлять поиск кода, выявлять источники ошибок, уменьшать дублирование кода и т. д., тем самым повышая эффективность разработки, уменьшая количество ошибок и снижая нагрузку на разработчиков.
В этом сценарии, в зависимости от ожидаемого качества генерации, он обычно состоит из двух моделей: одной большой и одной микро или одной средней и одной микро. Более крупная модель дает лучшие результаты с точки зрения качества генерации. Учитывая наш опыт проектирования в инструменте «Шоколадная фабрика», обычно такую функцию можно разделить на несколько этапов:
Как приложение RAG, оно разделено на две части: индексирование и запрос.
На этапе индексирования нам необходимо проиндексировать базу кода, что включает в себя сегментацию текста, векторизацию, индексацию базы данных и другие технологии. Одним из наиболее сложных элементов является разделение. Правила разделения, на которые мы ссылаемся: https://docs.sweep.dev/blogs/chunking-2m-files. Прямо сейчас:
В разных сценариях мы тоже можем делить по-разному. Например, в «Шоколадной фабрике» мы делим через AST, чтобы обеспечить качество генерируемого контекста.
На этапе запроса нам необходимо объединить некоторые наши традиционные технологии поиска, такие как векторизация поиска, поиск пути и т. д., чтобы обеспечить качество поиска. В то же время в китайском сценарии нам также необходимо рассмотреть вопрос преобразования на китайский язык, например, преобразование английского языка в китайский для обеспечения качества поиска.
Для ежедневной помощи мы также можем достичь этого с помощью генеративного искусственного интеллекта, например автоматического создания SQL DDL, автоматического создания тестовых примеров, автоматического создания требований и т. д. Этого можно достичь только путем настройки подсказок и объединения знаний в конкретной предметной области, поэтому я не буду здесь вдаваться в подробности.
Помимо модели, важным фактором, влияющим на возможности помощи ИИ, также является контекст. Когда мы создавали AutoDev, мы также обнаружили два разных контекстных режима:
Простое сравнение выглядит следующим образом:
соответствующий контекст | подобный контекст | |
---|---|---|
Технология поиска | статический анализ кода | Поиск по сходству |
информация о структуре данных | АСТ, КФГ | Похожие чанки |
Кроссплатформенные возможности | Зависит от IDE или независимого парсера | Не зависит от конкретных платформ |
контекстуальное качество | чрезвычайно высокий | высокий |
Генерация результатов | чрезвычайно высокий | высокий |
стоимость строительства | Зависит от языка и платформы | Низкий |
Когда поддержка IDE ограничена, контекстно-зависимая производительность будет выше.
GitHub Copilot использует аналогичный шаблон контекстной архитектуры, и его подробная архитектура состоит из следующих слоев:
В материалах исследования «публичного» проекта Copilot-Explorer можно увидеть, как устроен Prompt. Ниже приводится быстрый запрос, отправленный по адресу:
{
"prefix" : " # Path: codeviz \ app.py n #.... " ,
"suffix" : " if __name__ == '__main__': rn app.run(debug=True) " ,
"isFimEnabled" : true ,
"promptElementRanges" : [
{
"kind" : " PathMarker " ,
"start" : 0 ,
"end" : 23
},
{
"kind" : " SimilarFile " ,
"start" : 23 ,
"end" : 2219
},
{
"kind" : " BeforeCursor " ,
"start" : 2219 ,
"end" : 3142
}
]
}
в:
prefix
, используемая для создания приглашения, создается из PromptElements, который включает в себя: BeforeCursor
, AfterCursor
, SimilarFile
, ImportedFile
, LanguageMarker
, PathMarker
, RetrievalSnippet
и другие типы. Из названий нескольких PromptElementKind
мы также можем увидеть его истинное значение.suffix
часть, используемая для построения подсказки, определяется частью, в которой находится курсор. В соответствии с верхним пределом токенов (2048), сколько позиций осталось для расчета. Расчет токена здесь представляет собой настоящий расчет токена LLM. В Copilot он рассчитывается Cushman002. Длина токена китайских символов отличается, например: { context: "console.log('你好,世界')", lineCount: 1, tokenLength: 30 }
, где длина контента в контексте равна 20, но tokenLength равно 30, длина китайских иероглифов равна 5 (включая ,
), а фишка, занимаемая одним иероглифом, равна 3.Вот более подробный пример контекста приложения Java:
// Path: src/main/cc/unitmesh/demo/infrastructure/repositories/ProductRepository.java
// Compare this snippet from src/main/cc/unitmesh/demo/domain/product/Product.java:
// ....
// Compare this snippet from src/main/cc/unitmesh/demo/application/ProductService.java:
// ...
// @Component
// public class ProductService {
// //...
// }
//
package cc . unitmesh . demo . repositories ;
// ...
@ Component
public class ProductRepository {
//...
В контексте вычислений GitHub Copilot использует коэффициент Жаккара (сходство Жаккара). Эта часть реализации реализована в Agent. Более подробную логику можно найти в разделе: Потратив более полумесяца, я наконец перепроектировал Github Copilot.
Связанные ресурсы:
Как упоминалось выше, соответствующий код опирается на статический анализ кода , главным образом, с помощью структурной информации кода, такой как AST, CFG, DDG и т. д. В разных сценариях и на разных платформах мы можем комбинировать различные инструменты статического анализа кода. Ниже приведены некоторые распространенные инструменты статического анализа кода:
В сценарии завершения посредством статического анализа кода мы можем получить текущий контекст, например: текущую функцию, текущий класс, текущий файл и т. д. Ниже приведен пример контекста AutoDev для создания модульных тестов:
// here are related classes:
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/service/BlogService.java
// class BlogService {
// blogRepository
// + public BlogPost createBlog(BlogPost blogDto)
// + public BlogPost getBlogById(Long id)
// + public BlogPost updateBlog(Long id, BlogPost blogDto)
// + public void deleteBlog(Long id)
// }
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/dto/CreateBlogRequest.java
// class CreateBlogRequest ...
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/entity/BlogPost.java
// class BlogPost {...
@ ApiOperation ( value = "Create a new blog" )
@ PostMapping ( "/" )
public BlogPost createBlog ( @ RequestBody CreateBlogRequest request ) {
В этом примере контекст функции createBlog
анализируется для получения входных и выходных классов функции: CreateBlogRequest
, информации BlogPost
и информации класса BlogService, которые предоставляются модели в качестве контекста (предоставляются в комментариях). На этом этапе модель генерирует более точные конструкторы, а также более точные тестовые примеры.
Поскольку соответствующий контекст опирается на статический анализ кода разных языков и API разных IDE, нам также необходимо адаптироваться к разным языкам и разным IDE. Что касается стоимости строительства, то оно дороже по сравнению с аналогичными контекстами.
IDE и редакторы являются основными инструментами для разработчиков, а затраты на их разработку и обучение относительно высоки. Во-первых, мы можем использовать официальный шаблон для создания:
Потом добавить функционала сверху (не правда ли очень просто), конечно нет. Ниже приведены некоторые ресурсы плагинов IDEA для справки:
Конечно, уместнее обратиться к плагину AutoDev.
Вы можете напрямую использовать официальный шаблон для создания соответствующего плагина: https://github.com/JetBrains/intellij-platform-plugin-template.
Реализация плагина IDEA в основном реализуется через Action и Listener, которые нужно только зарегистрировать в plugin.xml
. Подробную информацию можно найти в официальной документации: IntelliJ Platform Plugin SDK.
Поскольку мы не рассматривали проблему совместимости с IDE-версией AutoDev на раннем этапе, чтобы впоследствии обеспечить совместимость со старой версией IDE, нам необходимо выполнить обработку совместимости над плагином. Таким образом, как описано в официальном документе: Диапазоны номеров сборок, мы видим, что разные версии предъявляют разные требования к JDK. Ниже приведены требования для разных версий:
Номер филиала | Версия платформы IntelliJ |
---|---|
233 | 2023.3 |
232 | 2023.2 |
231 | 2023.1 |
223 | 2022.3 |
222 | 2022.2 ПРИМЕЧАНИЕ. Теперь требуется Java 17 (сообщение в блоге). |
221 | 2022.1 |
213 | 2021.3 |
212 | 2021.2 |
211 | 2021.1 |
203 | 2020.3 ПРИМЕЧАНИЕ. Теперь требуется Java 11 (сообщение в блоге). |
И настройте его в gradle.properties
:
pluginSinceBuild = 223
pluginUntilBuild = 233.*
Последующая настройка совместимости затруднительна, поэтому можно обратиться к конструкции AutoDev.
Что касается автоматического завершения кода, отечественные производители в основном ссылаются на реализацию GitHub Copilot, и логика здесь не сложная.
Запуск с помощью сочетаний клавиш
В основном он отслеживает ввод пользователя в Action, а затем:
Функция | быстрая клавиша | иллюстрировать |
---|---|---|
запрос завершения | Alt + / | Получите текущий контекст, а затем получите результаты завершения через модель. |
применитьИнлеи | TAB | Отображение результатов завершения в IDE |
утилизироватьИнлеи | ESC | Отменить завершение |
циклСледующийИнлеи | Alt + ] | Перейти к следующему результату завершения |
циклPrevInlays | Alt + [ | Перейти к предыдущему результату завершения |
Использовать метод автоматического запуска
В основном он отслеживает ввод пользователя через EditorFactoryListener
, а затем запускает разные результаты завершения на основе разных входных данных. Основной код выглядит следующим образом:
class AutoDevEditorListener : EditorFactoryListener {
override fun editorCreated ( event : EditorFactoryEvent ) {
// ...
editor.document.addDocumentListener( AutoDevDocumentListener (editor), editorDisposable)
editor.caretModel.addCaretListener( AutoDevCaretListener (editor), editorDisposable)
// ...
}
class AutoDevCaretListener ( val editor : Editor ) : CaretListener {
override fun caretPositionChanged ( event : CaretEvent ) {
// ...
val wasTypeOver = TypeOverHandler .getPendingTypeOverAndReset(editor)
// ...
llmInlayManager.disposeInlays(editor, InlayDisposeContext . CaretChange )
}
}
class AutoDevDocumentListener ( val editor : Editor ) : BulkAwareDocumentListener {
override fun documentChangedNonBulk ( event : DocumentEvent ) {
// ...
val llmInlayManager = LLMInlayManager .getInstance()
llmInlayManager
.editorModified(editor, changeOffset)
}
}
}
Затем в соответствии с разными входными данными активируются разные результаты завершения и структура обрабатывается.
Код завершения рендеринга
Впоследствии нам нужно реализовать Inlay Render, который наследуется от EditorCustomElementRenderer
.
В сочетании с возможностями интерфейса IDE нам необходимо добавить соответствующее действие, соответствующую группу и соответствующий значок. Ниже приведен пример действия:
<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
Ниже приведены некоторые группы действий AutoDev:
Идентификатор группы | ИИ использует | Описание |
---|---|---|
Группа ШоуИнтеншенс | Рефакторинг кода, интерпретация кода, генерация кода, тестирование кода | Используется для отображения подсказок в контексте кода и доступен через сочетания клавиш Alt + Enter и ⌥ + Enter в macOS. |
Консольный редакторPopupMenu | исправлять ошибки | Меню, отображаемое в консоли, например консоли структуры выполнения программы. |
Вкс.MessageActionGroup | Генерация кодовой информации | Меню для написания сообщений коммитов в VCS. |
Vcs.Log.ContextMenu | Обзор кода, интерпретация кода, генерация кода | Меню просмотра логов в VCS, доступные функции: AI-инспекция кода, генерация логов релиза. |
РедакторВсплывающееМеню | Все приемлемы | В контекстном меню вы также можете добавить соответствующую группу действий. |
При написании ShowIntentionsGroup мы можем обратиться к реализации AutoDev для создания соответствующей группы:
< group id = " AutoDevIntentionsActionGroup " class = " cc.unitmesh.devti.intentions.IntentionsActionGroup "
icon = " cc.unitmesh.devti.AutoDevIcons.AI_COPILOT " searchable = " false " >
< add-to-group group-id = " ShowIntentionsGroup " relative-to-action = " ShowIntentionActions " anchor = " after " />
</ group >
Благодаря платформенной стратегии Intellij разница между работой в Java IDE (Intellij IDEA) и других IDE, таких как Python IDE (Pycharm), становится еще больше. Нам необходимо обеспечить совместимость на основе многоплатформенных продуктов. Подробное описание см. в разделе «Совместимость плагинов с продуктами платформы IntelliJ».
Во-первых, архитектура плагинов дополнительно модульна, то есть для разных языков предусмотрены разные модули. Ниже представлена модульная архитектура AutoDev:
java/ # Java 语言插件
src/main/java/cc/unitmesh/autodev/ # Java 语言入口
src/main/resources/META-INF/plugin.xml
plugin/ # 多平台入口
src/main/resources/META-INF/plugin.xml
src/ # 即核心模块
main/resource/META-INF/core.plugin.xml
В plugin/plugin.xml
нам нужно добавить соответствующие depends
и extensions
. Ниже приведен пример:
< idea-plugin package = " cc.unitmesh " xmlns : xi = " http://www.w3.org/2001/XInclude " allow-bundled-update = " true " >
< xi : include href = " /META-INF/core.xml " xpointer = " xpointer(/idea-plugin/*) " />
< content >
< module name = " cc.unitmesh.java " />
<!-- 其它模块 -->
</ content >
</ idea-plugin >
В java/plugin.xml
нам нужно добавить соответствующие depends
и extensions
. Ниже приведен пример:
< idea-plugin package = " cc.unitmesh.java " >
<!-- suppress PluginXmlValidity -->
< dependencies >
< plugin id = " com.intellij.modules.java " />
< plugin id = " org.jetbrains.plugins.gradle " />
</ dependencies >
</ idea-plugin >
Впоследствии Intellij автоматически загрузит соответствующий модуль для обеспечения многоязычной поддержки. В зависимости от того, какие языки мы ожидаем поддерживать, нам понадобится соответствующий plugin.xml
, например:
cc.unitmesh.javascript.xml
cc.unitmesh.rust.xml
cc.unitmesh.python.xml
cc.unitmesh.kotlin.xml
cc.unitmesh.java.xml
cc.unitmesh.go.xml
cc.unitmesh.cpp.xml
Наконец, просто реализуйте соответствующие функции в разных языковых модулях.
Чтобы упростить этот процесс, мы используем Unit Eval, чтобы показать, как построить два похожих контекста.
С помощью статического анализа кода мы можем получить текущую функцию, текущий класс, текущий файл и т. д. Затем объедините сходство путей, чтобы найти наиболее релевантный контекст.
private fun findRelatedCode ( container : CodeContainer ): List < CodeDataStruct > {
// 1. collects all similar data structure by imports if exists in a file tree
val byImports = container. Imports
.mapNotNull {
context.fileTree[it. Source ]?.container?. DataStructures
}
.flatten()
// 2. collects by inheritance tree for some node in the same package
val byInheritance = container. DataStructures
.map {
(it. Implements + it. Extend ).mapNotNull { i ->
context.fileTree[i]?.container?. DataStructures
}.flatten()
}
.flatten()
val related = (byImports + byInheritance).distinctBy { it. NodeName }
// 3. convert all similar data structure to uml
return related
}
class RelatedCodeStrategyBuilder ( private val context : JobContext ) : CodeStrategyBuilder {
override fun build (): List < TypedIns > {
// ...
val findRelatedCodeDs = findRelatedCode(container)
val relatedCodePath = findRelatedCodeDs.map { it. FilePath }
val jaccardSimilarity = SimilarChunker .pathLevelJaccardSimilarity(relatedCodePath, currentPath)
val relatedCode = jaccardSimilarity.mapIndexed { index, d ->
findRelatedCodeDs[index] to d
}.sortedByDescending {
it.second
}.take( 3 ).map {
it.first
}
// ...
}
}
Для приведенного выше кода мы можем использовать информацию об импорте кода как часть соответствующего кода. Затем найдите соответствующий код через отношения наследования кода. Наконец, ближайший контекст находится через сходство путей.
Сначала выполните поиск, а затем найдите связанный код по сходству кода. Основная логика показана:
fun pathLevelJaccardSimilarity ( chunks : List < String >, text : String ): List < Double > {
// ...
}
fun tokenize ( chunk : String ): List < String > {
return chunk.split( Regex ( " [^a-zA-Z0-9] " )).filter { it.isNotBlank() }
}
fun similarityScore ( set1 : Set < String >, set2 : Set < String >): Double {
// ...
}
Подробности см.: LikeChunker.
TODO
TreeSitter — это платформа для создания эффективных пользовательских парсеров, разработанная GitHub. Он использует парсер LR(1), что означает, что он может анализировать любой язык за время O(n) вместо времени O(n²). Он также использует технику, называемую «повторное использование синтаксического дерева», которая позволяет обновлять синтаксические деревья без повторного анализа всего файла.
Поскольку TreeSitter уже обеспечивает многоязычную поддержку, вы можете использовать Node.js, Rust и другие языки для создания соответствующих плагинов. Подробнее см.: TreeSitter.
В зависимости от наших намерений существуют разные способы использования TreeSitter:
Символ анализа
В поисковой системе естественного языка Bloop мы используем TreeSitter для анализа символов и достижения лучшего качества поиска.
; ; methods
(method_declaration
name: (identifier) @hoist.definition.method)
Затем решите, как его отображать в зависимости от типа:
pub static JAVA : TSLanguageConfig = TSLanguageConfig {
language_ids : & [ "Java" ] ,
file_extensions : & [ "java" ] ,
grammar : tree_sitter_java :: language ,
scope_query : MemoizedQuery :: new ( include_str ! ( "./scopes.scm" ) ) ,
hoverable_query : MemoizedQuery :: new (
r#"
[(identifier)
(type_identifier)] @hoverable
"# ,
) ,
namespaces : & [ & [
// variables
"local" ,
// functions
"method" ,
// namespacing, modules
"package" ,
"module" ,
// types
"class" ,
"enum" ,
"enumConstant" ,
"record" ,
"interface" ,
"typedef" ,
// misc.
"label" ,
] ] ,
} ;
Код фрагмента
Ниже показано, как TreeSitter используется для улучшения блока кода LlamaIndex путем очистки CST Tree-Sitter:
from tree_sitter import Tree
def chunker (
tree : Tree ,
source_code : bytes ,
MAX_CHARS = 512 * 3 ,
coalesce = 50 # Any chunk less than 50 characters long gets coalesced with the next chunk
) -> list [ Span ]:
# 1. Recursively form chunks based on the last post (https://docs.sweep.dev/blogs/chunking-2m-files)
def chunk_node ( node : Node ) -> list [ Span ]:
chunks : list [ Span ] = []
current_chunk : Span = Span ( node . start_byte , node . start_byte )
node_children = node . children
for child in node_children :
if child . end_byte - child . start_byte > MAX_CHARS :
chunks . append ( current_chunk )
current_chunk = Span ( child . end_byte , child . end_byte )
chunks . extend ( chunk_node ( child ))
elif child . end_byte - child . start_byte + len ( current_chunk ) > MAX_CHARS :
chunks . append ( current_chunk )
current_chunk = Span ( child . start_byte , child . end_byte )
else :
current_chunk += Span ( child . start_byte , child . end_byte )
chunks . append ( current_chunk )
return chunks
chunks = chunk_node ( tree . root_node )
# 2. Filling in the gaps
for prev , curr in zip ( chunks [: - 1 ], chunks [ 1 :]):
prev . end = curr . start
curr . start = tree . root_node . end_byte
# 3. Combining small chunks with bigger ones
new_chunks = []
current_chunk = Span ( 0 , 0 )
for chunk in chunks :
current_chunk += chunk
if non_whitespace_len ( current_chunk . extract ( source_code )) > coalesce