1+ import * as vscode from 'vscode' ;
12
2- import vscode = require( 'vscode' ) ;
3+ const SCALAR_TYPES = [
4+ 'double' ,
5+ 'float' ,
6+ 'int32' ,
7+ 'int64' ,
8+ 'uint32' ,
9+ 'uint64' ,
10+ 'sint32' ,
11+ 'sint64' ,
12+ 'fixed32' ,
13+ 'fixed64' ,
14+ 'sfixed32' ,
15+ 'sfixed64' ,
16+ 'bool' ,
17+ 'string' ,
18+ 'bytes' ,
19+ ] ;
320
4- class Proto3CompletionItemProvider implements vscode . CompletionItemProvider {
5- provideCompletionItems (
6- document : vscode . TextDocument ,
7- position : vscode . Position ,
8- token : vscode . CancellationToken ,
9- context : vscode . CompletionContext
10- ) : vscode . ProviderResult < vscode . CompletionItem [ ] | vscode . CompletionList < vscode . CompletionItem > > {
11- throw new Error ( 'Method not implemented.' ) ;
12- }
13- resolveCompletionItem ?( item : vscode . CompletionItem , token : vscode . CancellationToken ) : vscode . ProviderResult < vscode . CompletionItem > {
14- throw new Error ( 'Method not implemented.' ) ;
15- }
21+ const FILE_KEYWORDS = [
22+ 'syntax' ,
23+ 'package' ,
24+ 'import' ,
25+ 'option' ,
26+ 'message' ,
27+ 'enum' ,
28+ 'service' ,
29+ 'extend' ,
30+ ] ;
31+
32+ const FIELD_KEYWORDS = [
33+ 'repeated' ,
34+ 'optional' ,
35+ 'map' ,
36+ 'oneof' ,
37+ 'reserved' ,
38+ 'message' ,
39+ 'enum' ,
40+ ] ;
41+
42+ const SERVICE_KEYWORDS = [ 'rpc' , 'option' , 'stream' , 'returns' ] ;
43+
44+ const WELL_KNOWN_TYPES = [
45+ 'google.protobuf.Any' ,
46+ 'google.protobuf.Timestamp' ,
47+ 'google.protobuf.Duration' ,
48+ 'google.protobuf.Empty' ,
49+ 'google.protobuf.Struct' ,
50+ 'google.protobuf.Value' ,
51+ 'google.protobuf.ListValue' ,
52+ 'google.protobuf.FieldMask' ,
53+ 'google.protobuf.BoolValue' ,
54+ 'google.protobuf.BytesValue' ,
55+ 'google.protobuf.DoubleValue' ,
56+ 'google.protobuf.FloatValue' ,
57+ 'google.protobuf.Int32Value' ,
58+ 'google.protobuf.Int64Value' ,
59+ 'google.protobuf.StringValue' ,
60+ 'google.protobuf.UInt32Value' ,
61+ 'google.protobuf.UInt64Value' ,
62+ ] ;
63+
64+ type BlockKind = 'message' | 'enum' | 'service' | 'oneof' ;
65+
66+ function linePrefix ( document : vscode . TextDocument , position : vscode . Position ) : string {
67+ return document . lineAt ( position ) . text . substring ( 0 , position . character ) ;
68+ }
69+
70+ function stripLineComment ( line : string ) : string {
71+ return line . split ( '//' ) [ 0 ] ;
72+ }
73+
74+ function advanceBlockScan ( line : string , stack : BlockKind [ ] ) : void {
75+ const cleaned = stripLineComment ( line ) ;
76+ let idx = 0 ;
77+ while ( idx < cleaned . length ) {
78+ const rest = cleaned . slice ( idx ) ;
79+ const open = rest . match ( / ^ \b ( m e s s a g e | e n u m | s e r v i c e | o n e o f ) \s + \w + \s * \{ / ) ;
80+ if ( open ) {
81+ stack . push ( open [ 1 ] as BlockKind ) ;
82+ idx += open [ 0 ] . length ;
83+ continue ;
84+ }
85+ if ( rest [ 0 ] === '}' ) {
86+ stack . pop ( ) ;
87+ idx += 1 ;
88+ continue ;
89+ }
90+ idx += 1 ;
91+ }
1692}
1793
94+ function getScopeStack ( document : vscode . TextDocument , position : vscode . Position ) : BlockKind [ ] {
95+ const stack : BlockKind [ ] = [ ] ;
96+ for ( let line = 0 ; line <= position . line ; line ++ ) {
97+ let text = document . lineAt ( line ) . text ;
98+ if ( line === position . line ) {
99+ text = text . substring ( 0 , position . character ) ;
100+ }
101+ advanceBlockScan ( text , stack ) ;
102+ }
103+ return stack ;
104+ }
105+
106+ function getDeclaredTypes ( text : string ) : { messages : string [ ] ; enums : string [ ] } {
107+ const messages : string [ ] = [ ] ;
108+ const enums : string [ ] = [ ] ;
109+ for ( const m of text . matchAll ( / \b m e s s a g e \s + ( \w + ) \s * \{ / g) ) {
110+ messages . push ( m [ 1 ] ) ;
111+ }
112+ for ( const m of text . matchAll ( / \b e n u m \s + ( \w + ) \s * \{ / g) ) {
113+ enums . push ( m [ 1 ] ) ;
114+ }
115+ return {
116+ messages : [ ...new Set ( messages ) ] ,
117+ enums : [ ...new Set ( enums ) ] ,
118+ } ;
119+ }
120+
121+ function kw ( label : string , detail ?: string ) : vscode . CompletionItem {
122+ const i = new vscode . CompletionItem ( label , vscode . CompletionItemKind . Keyword ) ;
123+ if ( detail ) {
124+ i . detail = detail ;
125+ }
126+ return i ;
127+ }
128+
129+ function scalar ( label : string ) : vscode . CompletionItem {
130+ return new vscode . CompletionItem ( label , vscode . CompletionItemKind . TypeParameter ) ;
131+ }
132+
133+ function wellKnown ( label : string ) : vscode . CompletionItem {
134+ const i = new vscode . CompletionItem ( label , vscode . CompletionItemKind . Interface ) ;
135+ i . detail = 'Well-known type' ;
136+ return i ;
137+ }
18138
19- export { Proto3CompletionItemProvider } ;
139+ function addUserTypes (
140+ items : vscode . CompletionItem [ ] ,
141+ messages : string [ ] ,
142+ enums : string [ ]
143+ ) : void {
144+ for ( const name of messages ) {
145+ const i = new vscode . CompletionItem ( name , vscode . CompletionItemKind . Class ) ;
146+ i . detail = 'message' ;
147+ items . push ( i ) ;
148+ }
149+ for ( const name of enums ) {
150+ const i = new vscode . CompletionItem ( name , vscode . CompletionItemKind . Enum ) ;
151+ i . detail = 'enum' ;
152+ items . push ( i ) ;
153+ }
154+ }
155+
156+ function addRpcTypeItems (
157+ items : vscode . CompletionItem [ ] ,
158+ messages : string [ ] ,
159+ enums : string [ ]
160+ ) : void {
161+ for ( const t of WELL_KNOWN_TYPES ) {
162+ items . push ( wellKnown ( t ) ) ;
163+ }
164+ addUserTypes ( items , messages , enums ) ;
165+ }
166+
167+ function syntaxProto3Completion ( ) : vscode . CompletionItem [ ] {
168+ const i = new vscode . CompletionItem ( 'proto3' , vscode . CompletionItemKind . EnumMember ) ;
169+ i . detail = 'syntax = "proto3"' ;
170+ return [ i ] ;
171+ }
172+
173+ function isInsideUnclosedString ( prefix : string ) : boolean {
174+ return ( ( prefix . match ( / " / g) || [ ] ) . length % 2 ) === 1 ;
175+ }
176+
177+ export class Proto3CompletionItemProvider implements vscode . CompletionItemProvider {
178+ provideCompletionItems (
179+ document : vscode . TextDocument ,
180+ position : vscode . Position ,
181+ token : vscode . CancellationToken
182+ ) : vscode . ProviderResult < vscode . CompletionItem [ ] | vscode . CompletionList < vscode . CompletionItem > > {
183+ if ( token . isCancellationRequested ) {
184+ return [ ] ;
185+ }
186+
187+ const prefix = linePrefix ( document , position ) ;
188+ if ( isInsideUnclosedString ( prefix ) ) {
189+ if ( / \b s y n t a x \s * = \s * " ( \w * ) $ / . test ( prefix ) ) {
190+ return syntaxProto3Completion ( ) ;
191+ }
192+ return [ ] ;
193+ }
194+
195+ const fullText = document . getText ( ) ;
196+ const { messages, enums } = getDeclaredTypes ( fullText ) ;
197+
198+ if ( / \b r p c \s + \w + \s * \( \s * ( [ \w . ] * ) $ / . test ( prefix ) ) {
199+ const items : vscode . CompletionItem [ ] = [ ] ;
200+ addRpcTypeItems ( items , messages , enums ) ;
201+ return items ;
202+ }
203+ if ( / \b r e t u r n s \s * \( \s * ( [ \w . ] * ) $ / . test ( prefix ) ) {
204+ const items : vscode . CompletionItem [ ] = [ ] ;
205+ addRpcTypeItems ( items , messages , enums ) ;
206+ return items ;
207+ }
208+
209+ const stack = getScopeStack ( document , position ) ;
210+ const top = stack . length > 0 ? stack [ stack . length - 1 ] : undefined ;
211+
212+ if ( top === 'enum' ) {
213+ return [ kw ( 'reserved' ) , kw ( 'option' ) ] ;
214+ }
215+
216+ if ( top === 'service' ) {
217+ const items : vscode . CompletionItem [ ] = [ ] ;
218+ for ( const w of SERVICE_KEYWORDS ) {
219+ items . push ( kw ( w ) ) ;
220+ }
221+ return items ;
222+ }
223+
224+ if ( top === 'message' || top === 'oneof' ) {
225+ const items : vscode . CompletionItem [ ] = [ ] ;
226+ for ( const w of FIELD_KEYWORDS ) {
227+ items . push ( kw ( w ) ) ;
228+ }
229+ for ( const s of SCALAR_TYPES ) {
230+ items . push ( scalar ( s ) ) ;
231+ }
232+ for ( const t of WELL_KNOWN_TYPES ) {
233+ items . push ( wellKnown ( t ) ) ;
234+ }
235+ addUserTypes ( items , messages , enums ) ;
236+ return items ;
237+ }
238+
239+ // File scope (or unknown nested): top-level declarations + types for imports / forward refs
240+ const items : vscode . CompletionItem [ ] = [ ] ;
241+ for ( const w of FILE_KEYWORDS ) {
242+ items . push ( kw ( w ) ) ;
243+ }
244+ for ( const t of WELL_KNOWN_TYPES ) {
245+ items . push ( wellKnown ( t ) ) ;
246+ }
247+ addUserTypes ( items , messages , enums ) ;
248+ return items ;
249+ }
250+ }
0 commit comments