Service & Module Autocomplete

-- Handler.lua
--!nocheck
local ScriptEditorService = game:GetService("ScriptEditorService")
local ServerScriptService = game:GetService("ServerScriptService")

local AutocompleteHelper = require(script:WaitForChild("HelperFunctions"))
-- local WidgetHandler = require(script:WaitForChild("WidgetHandler"))
local Modules = {}
local Services = {}
local Active = false

--> Types
type Request = {
	position: {
		line: number,
		character: number,
	},
	textDocument: {
		document: ScriptDocument?,
		script: LuaSourceContainer?,
	},
}

type Response = {
	items: {
		{
			label: string,
			kind: Enum.CompletionItemKind?,
			tags: { Enum.CompletionItemTag }?,
			detail: string?,
			documentation: {
				value: string,
			}?,
			overloads: number?,
			learnMoreLink: string?,
			codeSample: string?,
			preselect: boolean?,
			textEdit: {
				newText: string,
				replace: {
					start: { line: number, character: number },
					["end"]: { line: number, character: number },
				},
			}?,
		}
	},
}

--> Functions
local function onChange(document: ScriptDocument, changesArray)
	local lineValues = document:GetLine()
	local Symbol = script.Parent.Symbol

	-- original hacky code xD
	Active = string.match(lineValues, Symbol.Value) ~= nil
end

local function onAdded(descendant: Instance)
	if not AutocompleteHelper.isModuleScript(descendant) then return end

	table.insert(Modules, descendant)
end

local function onRemoving(descendant: Instance)
	if not AutocompleteHelper.isModuleScript(descendant) then return end

	for i, module in ipairs(Modules) do
		if module == descendant then
			table.remove(Modules, i)
			break
		end
	end
end


local function getAllModulesAndServices()
	Services = {}
	Modules = {}
	
	for _, instance in ipairs(game:GetChildren()) do
		if AutocompleteHelper.isService(instance) then
			table.insert(Services, instance)
		end
	end

	for _, module in ipairs(game:GetDescendants()) do
		if AutocompleteHelper.isModuleScript(module) then
			table.insert(Modules, module)
		end
	end
end

local function autocompleteCallback(request, response)

	if not Active then 
		return response 
	end

	local replaceTemplate = nil
	
	for _, item in ipairs(response.items) do
		if item.textEdit then
			replaceTemplate = table.clone(item.textEdit.replace)
			replaceTemplate.start.character -= 1
			break
		end
	end

	if not replaceTemplate then return response end

	-- modules
	for _, module in ipairs(Modules) do
		local moduleName = tostring(module)

		if not AutocompleteHelper.shouldProcessName(moduleName) then continue end

		local moduleService = AutocompleteHelper.getServiceForModule(module)

		if not moduleService then continue end

		local path = AutocompleteHelper.getModuleFullNamePath(module)
		local finalText = AutocompleteHelper.getModuleInitializationString(moduleService, moduleName, path, request.textDocument.document)

		local item = {
			label = moduleName,
			detail = path,
			textEdit = {
				newText = finalText,
				replace = replaceTemplate
			}
		}

		table.insert(response.items, item)
	end

	-- services
	for _, service in ipairs(Services) do
		local serviceName = service.ClassName

		if not AutocompleteHelper.shouldProcessName(serviceName) then continue end

		local finalText = AutocompleteHelper.getServiceInitializationString(serviceName)

		local item = {
			label = serviceName,
			detail = "Service",
			textEdit = {
				newText = finalText,
				replace = replaceTemplate
			}
		}

		table.insert(response.items, item)
	end

	return response
end

local function createToolbar()
	-- local toolbar = plugin:CreateToolbar("AutoCompleteModules")
	-- local newButton = toolbar:CreateButton("Symbol Change", "Change the symbol used for autocomplete", "rbxassetid://11963352805")
	-- 
	-- local Widget = WidgetHandler:CreateWidget(plugin) -- module scripts dont have the plugin object so we pass dat
	-- 
	-- newButton.Click:Connect(function()
	-- 	Widget.Enabled = not Widget.Enabled 
	-- end)
	-- 
	-- Widget:BindToClose(function()
	-- 	newButton:SetActive(false)
	-- end)
end

--> Init
pcall(function()
	ScriptEditorService:DeregisterAutocompleteCallback("somenameitreallydoesntmatter")
end)

ScriptEditorService:RegisterAutocompleteCallback("somenameitreallydoesntmatter", 69, autocompleteCallback)
ScriptEditorService.TextDocumentDidChange:Connect(onChange)

game.DescendantAdded:Connect(onAdded)
game.DescendantRemoving:Connect(onRemoving)

getAllModulesAndServices()
-- HelperFunctions.lua
local AutocompleteHelper = {}

function AutocompleteHelper.isModuleScript(instance: Instance): boolean
	return instance.ClassName == "ModuleScript" and not instance:IsDescendantOf(game.CoreGui)
end

function AutocompleteHelper.isService(instance: Instance): boolean
	local service 
	
	local success, err = pcall(function()
		service = game:FindService(instance.ClassName)
	end)
	
	if success then
		return true
	else 
		return false
	end
end

function AutocompleteHelper.getServiceForModule(module: Instance): string?
	for _, service in pairs(game:GetChildren()) do
		if module:IsDescendantOf(service) then
			return tostring(service)
		end
	end
	return nil
end

function AutocompleteHelper.getModuleFullNamePath(module: Instance): string
	return module:GetFullName()
end

function AutocompleteHelper.shouldProcessName(name: string): boolean
	return name:match("[%w]+") == name and name:match("%d") == nil
end

function AutocompleteHelper.getModuleInitializationString(moduleService: string, moduleName: string, path: string, document: ScriptDocument): string
	local serviceAbstraction = string.format('local %s = game:GetService("%s")', moduleService, moduleService)
	local moduleAbstraction = string.format('local %s = require(%s)', moduleName, path)

	for i = 1, document:GetLineCount() do
		local lineString = document:GetLine(i)
		if lineString:match("local " .. moduleService) then
			return moduleAbstraction
		end
	end

	return serviceAbstraction .. "\n" .. moduleAbstraction
end

function AutocompleteHelper.getServiceInitializationString(serviceName: string): string
	return string.format('local %s = game:GetService("%s")', serviceName, serviceName)
end

return AutocompleteHelper

Heres the original code ^^

Alright i added everything to github. I think a good implementation i could make would be to instead of using prefix use directly the service name

Your opinions are: pointless because I type fast, a few plugins do that, use AI (can’t require modules like the plugin does), customisation is pointless… etc.

Just quit hating, every plugin helps people or not. I have sleitnick’s autocomplete plugin and it is way better for me to begin scripting by requiring all necessary modules, we all noticed this plugin won’t help you because you type as fast as an AI so why don’t you just ignore this topic???

Im not.


im just sharing my opinion that’s all. it was also seemly ended days ago. So…

I don’t care if it was days ago, your opinions are just useless and you’re just flooding the entire topic with yapping. So…

I don’t know why you’re this stressed over me expressing my opinion.

He is right tho. Constructive crictisism would have been better. It’s pointless commenting on others work an opinion that dosen’t help nobody.

1 Like

Alright, well it has been more than over for a bit now so id rather not keep going on about this.

Fair enough, but please for the next time try to give a more appropiate feedback on others developer open source creation such as telling them any bugs or how they could improve .

1 Like

You can actually do this by setting the symbol to nothing