@@ -113,6 +113,156 @@ void WorkspaceFolder::endAutocompletion(const lsp::CompletionParams& params)
113113 }
114114}
115115
116+ bool isGetService (const Luau::AstExpr* expr)
117+ {
118+ if (auto call = expr->as <Luau::AstExprCall>())
119+ if (auto index = call->func ->as <Luau::AstExprIndexName>())
120+ if (index->index == " GetService" )
121+ if (auto name = index->expr ->as <Luau::AstExprGlobal>())
122+ if (name->name == " game" )
123+ return true ;
124+
125+ return false ;
126+ }
127+
128+ struct ImportLocationVisitor : public Luau ::AstVisitor
129+ {
130+ std::unordered_map<std::string, size_t > serviceLineMap;
131+
132+ bool visit (Luau::AstStatLocal* local) override
133+ {
134+ if (local->vars .size != 1 )
135+ return false ;
136+
137+ auto localName = local->vars .data [0 ];
138+ auto expr = local->values .data [0 ];
139+
140+ if (!localName || !expr)
141+ return false ;
142+
143+ if (isGetService (expr))
144+ serviceLineMap.emplace (std::string (localName->name .value ), localName->location .begin .line );
145+
146+ return false ;
147+ }
148+
149+ bool visit (Luau::AstStatBlock* block) override
150+ {
151+ for (Luau::AstStat* stat : block->body )
152+ {
153+ stat->visit (this );
154+ }
155+
156+ return false ;
157+ }
158+ };
159+
160+ // / Attempts to retrieve a list of service names by inspecting the global type definitions
161+ static std::vector<std::string> getServiceNames (const Luau::ScopePtr scope)
162+ {
163+ std::vector<std::string> services;
164+
165+ if (auto dataModelType = scope->lookupType (" ServiceProvider" ))
166+ {
167+ if (auto ctv = Luau::get<Luau::ClassTypeVar>(dataModelType->type ))
168+ {
169+ if (auto getService = Luau::lookupClassProp (ctv, " GetService" ))
170+ {
171+ if (auto itv = Luau::get<Luau::IntersectionTypeVar>(getService->type ))
172+ {
173+ for (auto part : itv->parts )
174+ {
175+ if (auto ftv = Luau::get<Luau::FunctionTypeVar>(part))
176+ {
177+ auto it = Luau::begin (ftv->argTypes );
178+ auto end = Luau::end (ftv->argTypes );
179+
180+ if (it != end && ++it != end)
181+ {
182+ if (auto stv = Luau::get<Luau::SingletonTypeVar>(*it))
183+ {
184+ if (auto ss = Luau::get<Luau::StringSingleton>(stv))
185+ {
186+ services.emplace_back (ss->value );
187+ }
188+ }
189+ }
190+ }
191+ }
192+ }
193+ }
194+ }
195+ }
196+
197+ return services;
198+ }
199+
200+ void WorkspaceFolder::suggestImports (
201+ const Luau::ModuleName& moduleName, const Luau::Position& position, const ClientConfiguration& config, std::vector<lsp::CompletionItem>& result)
202+ {
203+ auto sourceModule = frontend.getSourceModule (moduleName);
204+ auto module = frontend.moduleResolverForAutocomplete .getModule (moduleName);
205+ if (!sourceModule || !module )
206+ return ;
207+
208+ // If in roblox mode - suggest services
209+ if (config.types .roblox )
210+ {
211+ auto scope = Luau::findScopeAtPosition (*module , position);
212+ if (!scope)
213+ return ;
214+
215+ // Place after any hot comments and TODO: already imported services
216+ size_t minimumLineNumber = 0 ;
217+ for (auto hotComment : sourceModule->hotcomments )
218+ {
219+ if (!hotComment.header )
220+ continue ;
221+ if (hotComment.location .begin .line >= minimumLineNumber)
222+ minimumLineNumber = hotComment.location .begin .line + 1 ;
223+ }
224+
225+ ImportLocationVisitor visitor;
226+ visitor.visit (sourceModule->root );
227+
228+ auto services = getServiceNames (frontend.typeCheckerForAutocomplete .globalScope );
229+ for (auto & service : services)
230+ {
231+ // ASSUMPTION: if the service was defined, it was defined with the exact same name
232+ bool isAlreadyDefined = false ;
233+ size_t lineNumber = minimumLineNumber;
234+ for (auto & [definedService, location] : visitor.serviceLineMap )
235+ {
236+ if (definedService == service)
237+ {
238+ isAlreadyDefined = true ;
239+ break ;
240+ }
241+
242+ if (definedService < service && location >= lineNumber)
243+ lineNumber = location + 1 ;
244+ }
245+
246+ if (isAlreadyDefined)
247+ continue ;
248+
249+ auto importText = " local " + service + " = game:GetService(\" " + service + " \" )\n " ;
250+
251+ lsp::CompletionItem item;
252+ item.label = service;
253+ item.kind = lsp::CompletionItemKind::Class;
254+ item.detail = " Auto-import" ;
255+ item.documentation = {lsp::MarkupKind::Markdown, codeBlock (" lua" , importText)};
256+ item.insertText = service;
257+
258+ lsp::Position placement{lineNumber, 0 };
259+ item.additionalTextEdits .emplace_back (lsp::TextEdit{{placement, placement}, importText});
260+
261+ result.emplace_back (item);
262+ }
263+ }
264+ }
265+
116266std::vector<lsp::CompletionItem> WorkspaceFolder::completion (const lsp::CompletionParams& params)
117267{
118268 auto config = client->getConfiguration (rootUri);
@@ -127,7 +277,9 @@ std::vector<lsp::CompletionItem> WorkspaceFolder::completion(const lsp::Completi
127277 return {};
128278 }
129279
130- auto result = Luau::autocomplete (frontend, fileResolver.getModuleName (params.textDocument .uri ), convertPosition (params.position ), nullCallback);
280+ auto moduleName = fileResolver.getModuleName (params.textDocument .uri );
281+ auto position = convertPosition (params.position );
282+ auto result = Luau::autocomplete (frontend, moduleName, position, nullCallback);
131283 std::vector<lsp::CompletionItem> items;
132284
133285 for (auto & [name, entry] : result.entryMap )
@@ -255,6 +407,12 @@ std::vector<lsp::CompletionItem> WorkspaceFolder::completion(const lsp::Completi
255407 items.emplace_back (item);
256408 }
257409
410+ if (config.completion .suggestImports &&
411+ (result.context == Luau::AutocompleteContext::Expression || result.context == Luau::AutocompleteContext::Statement))
412+ {
413+ suggestImports (moduleName, position, config, items);
414+ }
415+
258416 return items;
259417}
260418
0 commit comments