1
+ /**
2
+ * We follow the policy of only completing bracket pairs that we started
3
+ * But sometimes we started the pair in a previous autocomplete suggestion
4
+ */
5
+ export class BracketMatchingService {
6
+ private openingBracketsFromLastCompletion : string [ ] = [ ] ;
7
+ private lastCompletionFile : string | undefined = undefined ;
8
+
9
+ static BRACKETS : { [ key : string ] : string } = { "(" : ")" , "{" : "}" , "[" : "]" } ;
10
+ static BRACKETS_REVERSE : { [ key : string ] : string } = {
11
+ ")" : "(" ,
12
+ "}" : "{" ,
13
+ "]" : "[" ,
14
+ } ;
15
+
16
+ handleAcceptedCompletion ( completion : string , filepath : string ) {
17
+ this . openingBracketsFromLastCompletion = [ ] ;
18
+ const stack : string [ ] = [ ] ;
19
+
20
+ for ( let i = 0 ; i < completion . length ; i ++ ) {
21
+ const char = completion [ i ] ;
22
+ if ( Object . keys ( BracketMatchingService . BRACKETS ) . includes ( char ) ) {
23
+ // It's an opening bracket
24
+ stack . push ( char ) ;
25
+ } else if (
26
+ Object . values ( BracketMatchingService . BRACKETS ) . includes ( char )
27
+ ) {
28
+ // It's a closing bracket
29
+ if (
30
+ stack . length === 0 ||
31
+ BracketMatchingService . BRACKETS [ stack . pop ( ) ! ] !== char
32
+ ) {
33
+ break ;
34
+ }
35
+ }
36
+ }
37
+
38
+ // Any remaining opening brackets in the stack are uncompleted
39
+ this . openingBracketsFromLastCompletion = stack ;
40
+ this . lastCompletionFile = filepath ;
41
+ }
42
+
43
+ async * stopOnUnmatchedClosingBracket (
44
+ stream : AsyncGenerator < string > ,
45
+ suffix : string ,
46
+ filepath : string ,
47
+ ) : AsyncGenerator < string > {
48
+ // Add opening brackets from the previous response
49
+ let stack : string [ ] = [ ] ;
50
+ if ( this . lastCompletionFile === filepath ) {
51
+ stack = [ ...this . openingBracketsFromLastCompletion ] ;
52
+ } else {
53
+ this . lastCompletionFile = undefined ;
54
+ }
55
+
56
+ // Add corresponding open brackets from suffix to stack
57
+ for ( let i = 0 ; i < suffix . length ; i ++ ) {
58
+ if ( suffix [ i ] === " " ) continue ;
59
+ const openBracket = BracketMatchingService . BRACKETS_REVERSE [ suffix [ i ] ] ;
60
+ if ( ! openBracket ) break ;
61
+ stack . unshift ( openBracket ) ;
62
+ }
63
+
64
+ let all = "" ;
65
+ let seenNonWhitespaceOrClosingBracket = false ;
66
+ for await ( let chunk of stream ) {
67
+ // Allow closing brackets before any non-whitespace characters
68
+ if ( ! seenNonWhitespaceOrClosingBracket ) {
69
+ const firstNonWhitespaceOrClosingBracketIndex =
70
+ chunk . search ( / [ ^ \s \) \} \] ] / ) ;
71
+ if ( firstNonWhitespaceOrClosingBracketIndex !== - 1 ) {
72
+ yield chunk . slice ( 0 , firstNonWhitespaceOrClosingBracketIndex ) ;
73
+ chunk = chunk . slice ( firstNonWhitespaceOrClosingBracketIndex ) ;
74
+ seenNonWhitespaceOrClosingBracket = true ;
75
+ } else {
76
+ yield chunk ;
77
+ continue ;
78
+ }
79
+ }
80
+
81
+ all += chunk ;
82
+ const allLines = all . split ( "\n" ) ;
83
+ for ( let i = 0 ; i < chunk . length ; i ++ ) {
84
+ const char = chunk [ i ] ;
85
+ if ( Object . values ( BracketMatchingService . BRACKETS ) . includes ( char ) ) {
86
+ // It's a closing bracket
87
+ if (
88
+ stack . length === 0 ||
89
+ BracketMatchingService . BRACKETS [ stack . pop ( ) ! ] !== char
90
+ ) {
91
+ // If the stack is empty or the top of the stack doesn't match the current closing bracket
92
+ yield chunk . slice ( 0 , i ) ;
93
+ return ; // Stop the generator if the closing bracket doesn't have a matching opening bracket in the stream
94
+ }
95
+ } else if (
96
+ Object . keys ( BracketMatchingService . BRACKETS ) . includes ( char )
97
+ ) {
98
+ // It's an opening bracket
99
+ stack . push ( char ) ;
100
+ }
101
+ }
102
+ yield chunk ;
103
+ }
104
+ }
105
+ }
0 commit comments