11import * as vscode from 'vscode' ;
22import * as fs from 'fs' ;
33import * as path from 'path' ;
4+ import * as readline from 'readline' ;
45
56interface FileTreeItem extends vscode . TreeItem {
67 children ?: FileTreeItem [ ] ;
78 fullPath : string ;
89 excluded : boolean ;
910}
1011
12+ interface ExclusionSettings {
13+ autoExcludePatterns : string [ ] ;
14+ respectGitignore : boolean ;
15+ autoExcludeEnabled : boolean ;
16+ }
17+
1118class RepoAnalyzerProvider implements vscode . TreeDataProvider < FileTreeItem > {
1219 private _onDidChangeTreeData = new vscode . EventEmitter < FileTreeItem | undefined | void > ( ) ;
1320 readonly onDidChangeTreeData = this . _onDidChangeTreeData . event ;
@@ -16,11 +23,13 @@ class RepoAnalyzerProvider implements vscode.TreeDataProvider<FileTreeItem> {
1623 private workspaceRoot : string | undefined ;
1724 private _useAIStyle : boolean = false ;
1825 private fileWatcher : vscode . FileSystemWatcher | undefined ;
26+ private gitignorePatterns : string [ ] = [ ] ;
1927
2028 constructor ( context : vscode . ExtensionContext ) {
2129 if ( vscode . workspace . workspaceFolders ) {
2230 this . workspaceRoot = vscode . workspace . workspaceFolders [ 0 ] . uri . fsPath ;
2331 this . setupFileWatcher ( ) ;
32+ this . initializeExclusions ( ) . then ( ( ) => { this . refresh ( ) ; } ) ;
2433 }
2534
2635 this . _useAIStyle = context . globalState . get ( 'repotxt.useAIStyle' , false ) ;
@@ -29,13 +38,139 @@ class RepoAnalyzerProvider implements vscode.TreeDataProvider<FileTreeItem> {
2938 if ( vscode . workspace . workspaceFolders ) {
3039 this . workspaceRoot = vscode . workspace . workspaceFolders [ 0 ] . uri . fsPath ;
3140 this . setupFileWatcher ( ) ;
32- this . refresh ( ) ;
41+ this . initializeExclusions ( ) . then ( ( ) => { this . refresh ( ) ; } ) ;
42+ }
43+ } ) ;
44+
45+ vscode . workspace . onDidChangeConfiguration ( e => {
46+ if ( e . affectsConfiguration ( 'repotxt' ) ) {
47+ this . initializeExclusions ( ) . then ( ( ) => { this . refresh ( ) ; } ) ;
48+ }
49+ } ) ;
50+ }
51+
52+ private async checkForAutoExclusion ( filePath : string ) {
53+ const config = vscode . workspace . getConfiguration ( 'repotxt' ) ;
54+ const autoExcludeEnabled = config . get ( 'autoExcludeEnabled' , true ) ;
55+
56+ if ( ! autoExcludeEnabled ) {
57+ return ;
58+ }
59+
60+ const patterns = config . get < string [ ] > ( 'autoExcludePatterns' , [ ] ) ;
61+ const fileName = path . basename ( filePath ) ;
62+ const relativePath = path . relative ( this . workspaceRoot ! , filePath ) ;
63+
64+ const shouldExclude = patterns . some ( pattern => {
65+ if ( pattern === fileName || pattern === relativePath ) {
66+ return true ;
3367 }
68+
69+ if ( pattern . includes ( '*' ) ) {
70+ const regex = new RegExp ( '^' + pattern . replace ( / \* / g, '.*' ) + '$' ) ;
71+ return regex . test ( fileName ) || regex . test ( relativePath ) ;
72+ }
73+
74+ return false ;
3475 } ) ;
76+
77+ if ( shouldExclude ) {
78+ this . excludedPaths . add ( filePath ) ;
79+ if ( fs . statSync ( filePath ) . isDirectory ( ) ) {
80+ this . excludeDirectory ( filePath ) ;
81+ }
82+ }
83+ }
84+
85+ private async initializeExclusions ( ) {
86+ if ( ! this . workspaceRoot ) return ;
87+
88+ const config = vscode . workspace . getConfiguration ( 'repotxt' ) ;
89+ const settings : ExclusionSettings = {
90+ autoExcludeEnabled : config . get ( 'autoExcludeEnabled' , true ) ,
91+ autoExcludePatterns : config . get ( 'autoExcludePatterns' , [
92+ 'node_modules' ,
93+ '.git' ,
94+ 'dist' ,
95+ 'build' ,
96+ 'out' ,
97+ 'coverage' ,
98+ '.env' ,
99+ '*.log' ,
100+ 'package-lock.json' ,
101+ 'yarn.lock'
102+ ] ) ,
103+ respectGitignore : config . get ( 'respectGitignore' , true )
104+ } ;
105+
106+ this . excludedPaths . clear ( ) ;
107+
108+ if ( ! settings . autoExcludeEnabled ) {
109+ return ;
110+ }
111+
112+ if ( settings . autoExcludePatterns . length > 0 ) {
113+ await this . processExcludePatterns ( settings . autoExcludePatterns ) ;
114+ }
115+
116+ if ( settings . respectGitignore ) {
117+ await this . processGitignore ( ) ;
118+ }
119+ }
120+
121+ private async processGitignore ( ) {
122+ if ( ! this . workspaceRoot ) return ;
123+
124+ const gitignorePath = path . join ( this . workspaceRoot , '.gitignore' ) ;
125+ if ( ! fs . existsSync ( gitignorePath ) ) return ;
126+
127+ try {
128+ const fileStream = fs . createReadStream ( gitignorePath ) ;
129+ const rl = readline . createInterface ( {
130+ input : fileStream ,
131+ crlfDelay : Infinity
132+ } ) ;
133+
134+ this . gitignorePatterns = [ ] ;
135+ for await ( const line of rl ) {
136+ if ( line && ! line . startsWith ( '#' ) && line . trim ( ) ) {
137+ this . gitignorePatterns . push ( line . trim ( ) ) ;
138+ }
139+ }
140+
141+ await this . processExcludePatterns ( this . gitignorePatterns ) ;
142+ } catch ( error ) {
143+ console . error ( 'Error processing .gitignore:' , error ) ;
144+ }
145+ }
146+
147+ private async processExcludePatterns ( patterns : string [ ] ) {
148+ if ( ! this . workspaceRoot ) return ;
149+
150+ for ( const pattern of patterns ) {
151+ const fullPattern = path . join ( this . workspaceRoot , pattern ) ;
152+
153+ try {
154+ // Handle glob patterns
155+ const matches = await vscode . workspace . findFiles ( pattern ) ;
156+ matches . forEach ( uri => {
157+ this . excludedPaths . add ( uri . fsPath ) ;
158+ } ) ;
159+
160+ // Handle direct paths
161+ if ( fs . existsSync ( fullPattern ) ) {
162+ this . excludedPaths . add ( fullPattern ) ;
163+ if ( fs . statSync ( fullPattern ) . isDirectory ( ) ) {
164+ this . excludeDirectory ( fullPattern ) ;
165+ }
166+ }
167+ } catch ( error ) {
168+ console . error ( `Error processing pattern ${ pattern } :` , error ) ;
169+ }
170+ }
35171 }
36172
37173 private setupFileWatcher ( ) {
38- // Очищаем предыдущий watcher если он был
39174 if ( this . fileWatcher ) {
40175 this . fileWatcher . dispose ( ) ;
41176 }
@@ -45,19 +180,20 @@ class RepoAnalyzerProvider implements vscode.TreeDataProvider<FileTreeItem> {
45180 new vscode . RelativePattern ( this . workspaceRoot , '**/*' )
46181 ) ;
47182
48- this . fileWatcher . onDidCreate ( ( ) => this . refresh ( ) ) ;
183+ this . fileWatcher . onDidCreate ( async ( uri ) => {
184+ await this . checkForAutoExclusion ( uri . fsPath ) ;
185+ this . refresh ( ) ;
186+ } ) ;
49187 this . fileWatcher . onDidDelete ( ( ) => this . refresh ( ) ) ;
50188 this . fileWatcher . onDidChange ( ( ) => this . refresh ( ) ) ;
51189 }
52190 }
53191
54192 private isPathExcluded ( fullPath : string ) : boolean {
55- // Проверяем сам путь
56193 if ( this . excludedPaths . has ( fullPath ) ) {
57194 return true ;
58195 }
59196
60- // Проверяем, находится ли путь в исключенной папке
61197 for ( const excludedPath of this . excludedPaths ) {
62198 if ( fullPath . startsWith ( excludedPath + path . sep ) ) {
63199 return true ;
@@ -93,7 +229,6 @@ class RepoAnalyzerProvider implements vscode.TreeDataProvider<FileTreeItem> {
93229 }
94230 }
95231
96- // Добавляем геттер для useAIStyle
97232 get useAIStyle ( ) : boolean {
98233 return this . _useAIStyle ;
99234 }
@@ -115,7 +250,6 @@ class RepoAnalyzerProvider implements vscode.TreeDataProvider<FileTreeItem> {
115250 treeItem . iconPath = new vscode . ThemeIcon ( 'eye-closed' ) ;
116251 treeItem . description = '(excluded)' ;
117252 treeItem . tooltip = 'Excluded from report' ;
118- treeItem . resourceUri = vscode . Uri . parse ( `excluded:${ element . fullPath } ` ) ;
119253 } else {
120254 treeItem . iconPath = element . collapsibleState === vscode . TreeItemCollapsibleState . None ?
121255 new vscode . ThemeIcon ( 'file' ) :
@@ -307,12 +441,17 @@ export function activate(context: vscode.ExtensionContext) {
307441 showCollapseAll : true
308442 } ) ;
309443
310- // Create status bar item for AI Style toggle
311444 aiStatusBarItem = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Right ) ;
312445 aiStatusBarItem . command = 'repotxt.toggleAIStyle' ;
446+ context . subscriptions . push ( aiStatusBarItem ) ;
447+
448+ function updateAIStatusBarItem ( useAIStyle : boolean ) {
449+ aiStatusBarItem . text = useAIStyle ? '$(sparkle) AI Style' : '$(symbol-boolean) Regular Style' ;
450+ aiStatusBarItem . tooltip = useAIStyle ? 'Click to disable AI Style' : 'Click to enable AI Style' ;
451+ }
452+
313453 updateAIStatusBarItem ( repoAnalyzerProvider . useAIStyle ) ;
314454 aiStatusBarItem . show ( ) ;
315- context . subscriptions . push ( aiStatusBarItem ) ;
316455
317456 context . subscriptions . push (
318457 vscode . commands . registerCommand ( 'repotxt.refresh' , ( ) => {
@@ -351,13 +490,4 @@ export function activate(context: vscode.ExtensionContext) {
351490 ) ;
352491}
353492
354- function updateAIStatusBarItem ( useAIStyle : boolean ) {
355- aiStatusBarItem . text = useAIStyle ? '$(sparkle) AI Style' : '$(symbol-boolean) Regular Style' ;
356- aiStatusBarItem . tooltip = useAIStyle ? 'Click to disable AI Style' : 'Click to enable AI Style' ;
357- }
358-
359- export function deactivate ( ) {
360- if ( aiStatusBarItem ) {
361- aiStatusBarItem . dispose ( ) ;
362- }
363- }
493+ export function deactivate ( ) { }
0 commit comments