2023 年には、生成 AI の人気により、AI 支援コーディングを導入する組織がますます増えるでしょう。 2021 年にリリースされた GitHub Copilot と少し異なるのは、コード補完が多くのシナリオの 1 つにすぎないことです。 多くの企業が、完全なコードの生成や要件に基づいたコードレビューなどのシナリオを検討しており、開発効率を向上させるために生成 AI も導入しています。
これに関連して、私たち (Thoughtworks オープンソース コミュニティ) は、より多くの組織が独自の AI 支援コーディング アシスタントを構築できるように、一連の AI 補助ツールもオープンソース化しました。
私たちが AutoDev を設計したとき、さまざまなオープンソース モデルが絶えず進化していたからです。この文脈での手順は次のとおりです。
したがって、このチュートリアルもこれら 3 つのステップを中心に説明します。 さらに、私たちの経験に基づいて、このチュートリアルのサンプル テクノロジー スタックは次のとおりです。
AI に関する私たちの経験は比較的限られているため、間違いが生じることは避けられません。そのため、私たちはより多くの開発者と協力してこのオープンソース プロジェクトを構築したいと考えています。
JetBrains 2023 年の「開発者エコシステム」レポートの人工知能部分と組み合わせると、開発プロセスで生成 AI が役割を果たすことができる領域を反映するいくつかの一般的なシナリオを要約できます。主なシナリオのいくつかを次に示します。
AutoDev を構築したとき、SQL DDL の作成、要件の生成、TDD などのシナリオも発見しました。それで。開発者が独自の AI 機能をカスタマイズできるように、シナリオをカスタマイズする機能が提供されています。詳細については、https://ide.unitmesh.cc/customize を参照してください。
日常的なコーディングでは、AI の応答速度に関するさまざまな要件を持ついくつかの異なるシナリオがあります (一例として)。
シーン | 応答速度 | 品質要件の生成 | 予想されるサイズ | 説明する |
---|---|---|---|---|
コード補完 | 素早い | 真ん中 | 1~6B | コード補完は日常のコーディングで最も一般的なシナリオであり、応答速度が非常に重要です。 |
ドキュメントの生成 | 真ん中 | 真ん中 | 1 | ドキュメントの生成にはコード構造を完全に理解する必要があり、速度と品質も同様に重要です。 |
コードレビュー | 素早い | 真ん中 | 1 | コードレビューには質の高いアドバイスが必要ですが、できるだけ迅速に対応する必要もあります。 |
単体テストの生成 | 素早い | 真ん中 | 6B~ | 単体テストでは生成されるコンテキストが少なく、応答性と AI の品質も同様に重要です。 |
コードのリファクタリング | 真ん中 | 高い | 32B~ | コードのリファクタリングには、より文脈の理解が必要な場合があり、応答時間が多少遅くなる可能性があります。 |
需要の創出 | 真ん中 | 高い | 32B~ | 需要生成は比較的複雑なシナリオであるため、精度を確保するために応答速度を適度に遅くすることができます。 |
自然言語コードの検索と解釈 | 中~低 | 高い | 32B~ | 自然言語コードの検索と解釈は比較的複雑なシナリオであり、精度を確保するために応答速度が適度に遅くなることがあります。 |
PS: ここでの 32B は、より大きなモデルの方が効果が優れているため、桁数としてのみ表現されています。
したがって、これを次のように要約します。1つの大規模モデル、1 つの中規模モデル、1 つのマイクロモデル、および3 つのモデルで、包括的な AI 支援コーディングを提供します。
AI コード補完は、IDE ツールを組み合わせてコードのコンテキストとプログラミング言語のルールを分析し、AI がコード スニペットを自動的に生成または提案します。 GitHub Copilot に似たコード補完ツールでは、通常、次の 3 つのサブディビジョン モードに分割されます。
インライン補完 (インライン)
FIM (fill in the middle) モードと同様に、完成したコンテンツは現在の行にあります。例: 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)
アフターブロック
コンテキスト学習 (インコンテキスト学習) を通じて達成され、現在の機能ブロックの後に完了します。たとえば、現在の機能ブロックの後に新しい関数が完了します。たとえば、元のコードは次のとおりです。
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
// ...
}
完成したコードは次のとおりです。
fun updateBlog ( id : Long , blogDto : CreateBlogDto ): BlogPost {
// ...
}
fun deleteBlog ( id : Long ) {
// ...
}
対応する AI 補完機能を構築するときは、補完の品質を向上させ、より良いユーザー エクスペリエンスを提供するために、それを対応するパターン データ セットに適用することも考慮する必要があります。
この記事を書くための関連リソース:
コードの説明は、開発者が大規模なコード ベースをより効果的に管理および理解できるように設計されています。これらのアシスタントは、コード ベースに関する質問に答え、ドキュメントを提供し、コードを検索し、エラーの原因を特定し、コードの重複を減らすなどの機能を備えているため、開発効率が向上し、エラー率が減少し、開発者の作業負荷が軽減されます。
このシナリオでは、期待する生成の品質に応じて、通常は 2 つのモデルで構成されます。1 つは大規模モデルと 1 つはマイクロ モデル、または 1 つは中規模モデルと 1 つはマイクロ モデルです。 Chocolate Factory ツールでの設計経験と組み合わせると、通常、このような機能はいくつかのステップに分割できます。
RAG アプリケーションは、インデックス作成とクエリの 2 つの部分に分かれています。
インデックス作成段階では、コード ベースのインデックスを作成する必要があります。これには、テキストのセグメント化、ベクトル化、データベースのインデックス作成などのテクノロジが含まれます。 最も困難な要素の 1 つは分割です。ここで参照する分割ルールは https://docs.スイープ.dev/blogs/chunking-2m-files です。今すぐ:
さまざまなシナリオでは、さまざまな方法で分割することもできます。たとえば、Chocolate Factory では、生成されたコンテキストの品質を確保するために AST を使用して分割します。
クエリの段階では、検索の品質を確保するために、ベクトル化検索、パス検索などの従来の検索テクノロジーのいくつかを組み合わせる必要があります。同時に、中国語のシナリオでは、検索の品質を確保するために英語を中国語に変換するなど、中国語への変換の問題も考慮する必要があります。
日常的な支援については、SQL DDL の自動作成、テスト ケースの自動作成、要件の自動作成など、生成 AI を通じて実現することもできます。これらは、プロンプトの単語をカスタマイズし、特定の分野の知識を組み合わせることでのみ実現できるため、ここでは詳しく説明しません。
モデルに加えて、コンテキストも AI 支援機能に影響を与える重要な要素です。 AutoDev を構築したとき、次の 2 つの異なるコンテキスト モードも発見しました。
簡単な比較は次のとおりです。
関連するコンテキスト | 似たような文脈 | |
---|---|---|
検索技術 | 静的コード分析 | 類似検索 |
データ構造情報 | AST、CFG | 類似のチャンク |
クロスプラットフォーム機能 | 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
部分は、プロンプト要素から構築されます。これには、 BeforeCursor
、 AfterCursor
、 SimilarFile
、 ImportedFile
、 LanguageMarker
、 PathMarker
、 RetrievalSnippet
およびその他のタイプが含まれます。いくつかのPromptElementKind
の名前からも、その本当の意味が分かります。suffix
部分は、カーソルが置かれている部分と、トークンの上限 (2048) に基づいて計算される残りの位置の数によって決まります。ここでのトークン計算は、Copilot では Cushman002 によって計算されます。 { context: "console.log('你好,世界')", lineCount: 1, tokenLength: 30 }
、コンテキスト内のコンテンツの長さは 20 ですが、tokenLengthは 30、漢字の長さは 5 ( ,
を含む)、1 文字が占めるトークンは 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 は Jaccard 係数 (Jaccard 類似性) を使用します。実装のこの部分はエージェントに実装されています。詳細なロジックについては、半月以上を費やして、最終的に 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 クラス情報を取得します。これらはコンテキストとしてモデルに提供されます (コメントで提供されます)。この時点で、モデルはより正確なコンストラクターとより正確なテスト ケースを生成します。
関連するコンテキストはさまざまな言語の静的コード分析とさまざまな IDE の API に依存しているため、さまざまな言語やさまざまな IDE に適応する必要もあります。建設コストの点では、同様の状況に比べて高価です。
IDE とエディタは開発者にとっての主要なツールであり、その設計と学習のコストは比較的高くなります。まず、公式テンプレートを使用して以下を生成できます。
次に、その上に機能を追加します (非常に単純ですね)。もちろんそうではありません。以下に、参照できる IDEA プラグインのリソースをいくつか示します。
もちろん、AutoDev プラグインを参照する方が適切です。
公式テンプレートを直接使用して、対応するプラグインを生成できます: https://github.com/JetBrains/intellij-platform-plugin-template
IDEA プラグインの実装の場合、主に Action と Listener を通じて実装されます。これらはplugin.xml
に登録するだけで済みます。 詳細については、公式ドキュメントを参照してください: IntelliJ プラットフォーム プラグイン SDK
初期段階では AutoDev の IDE バージョンとの互換性の問題を考慮していなかったので、後から古いバージョンの 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の実装を参考にしており、ロジックも複雑ではありません。
ショートカットキーを使用したトリガー
主にアクションでのユーザー入力を監視し、次のことを行います。
関数 | ショートカットキー | 説明する |
---|---|---|
リクエスト完了 | Alt + / | 現在のコンテキストを取得し、モデルを通じて完了結果を取得します。 |
インレイを適用する | TAB | 完了結果をIDE上に表示する |
インレイの処分 | ESC | キャンセル完了 |
サイクルネクストインレイ | Alt + ] | 次の完了結果に切り替える |
サイクル前のインレイ | 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)
}
}
}
次に、さまざまな入力に従って、さまざまな完了結果がトリガーされ、構造が処理されます。
レンダリング完了コード
次に、 EditorCustomElementRenderer
を継承する Inlay Render を実装する必要があります。
IDE のインターフェイス機能と組み合わせて、対応するアクション、対応するグループ、および対応するアイコンを追加する必要があります。以下はアクションの例です。
<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
AutoDev の ActionGroup の一部を次に示します。
グループID | AI が使用する | 説明 |
---|---|---|
ShowIntentionsGroup | コードリファクタリング、コード解釈、コード生成、コードテスト | コード コンテキストでヒントを表示するために使用され、macOS ではAlt + Enter および⌥ + Enter ショートカットからアクセスできます。 |
コンソールエディタポップアップメニュー | エラーを修正する | プログラム実行機構のコンソールなどに表示されるメニュー。 |
Vcs.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) と Python IDE (Pycharm) などの他の IDE で実行する場合の違いはさらに大きくなります。マルチプラットフォーム製品に基づいた互換性を提供する必要があります。詳細については、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 を使用して 2 つの類似したコンテキストを構築する方法を示します。
静的コード分析を通じて、現在の関数、現在のクラス、現在のファイルなどを取得できます。パスの類似性と組み合わせて、最も関連性の高いコンテキストを見つけます。
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 {
// ...
}
詳細については、「SimilarChunker」を参照してください。
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" ,
] ] ,
} ;
チャンクコード
以下は、Tree-Sitter CST のクリーニングによる LlamaIndex のコード チャンカーの改善における TreeSitter の使用方法です。
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