CMD+ - Execute Custom Commands

image



Use this extremely versatile plugin to improve your workflow on Roblox Studio by quickly executing custom shortcuts.

How the plugin works:

Fun fact: it took me 3 revisions of the plugin with 3 different implementations to arrive at the ideal method.

When you press the shortcut key, a new object Chain is created. This object is responsible for handling all the Nodes in a refined matter. It kind of resembles the idea of a chain with perfect links between, hence the name Chain. The CommandNode always comes first in the Chain. The next Nodes are created as they are defined in your command script. All of the Nodes get inherited from a superclass called Node. This is to allow easy access to add extra nodes in the future and maintain a good code structure.

Visual demo

The example above is a default command using the code:

local changeHistoryService = game:GetService("ChangeHistoryService")
local selectionService = game:GetService("Selection")

return {

	metadata = {
		id = "DefaultCMD:RenameObjects";   --unique id for every command
		display = "Rename object(s)";   --display-text for command
		shortcut = {Enum.KeyCode.LeftShift, Enum.KeyCode.R}; --optional shortcut
		inputRequired = false;   --enable if command requires input
	};

	execute = function(chain)
		
		--Create a new ChoiceNode with optional settings and make sure input was recieved
		local selectionType = chain:Node("ChoiceNode"):Set({
			title = "Selection type";
			text = "Select object(s) from...";
			choices = {
				{"s", "selection"};
				{"n", "name"};
			};
		}):Get()
		if not selectionType then return end
		
		--If selection is by name, get selection ancestor
		local service
		if selectionType == "n" then
			local list = {{"game", "All"}}
			for _, service in ipairs(game:GetChildren()) do
				pcall(function()
					table.insert(list, {service.ClassName, service.Name})
				end)
			end
			
			service = chain:Node("ListNode"):Set({
				title = "Search for object(s) in";
				placeholder = "enter service name...";
				list = list;
			}):Get()
			if not service then return end
			
			if service == "game" then
				service = game	
			else
				service = game:GetService(service)
			end
		end
		
		
		--Get selection
		local selection
		if selectionType == "n" then
			local name, ignoreCase = chain:Node("InputNode"):Set({
				title = "Select object(s) by name";
				placeholder = "enter name...";
			}):Get()
			if not name then return end
			
			local toSet = {}
			for _, obj in ipairs(service:GetDescendants()) do
				if (string.lower(obj.Name) == string.lower(name) and ignoreCase) or (not ignoreCase and obj.Name == name) then
					table.insert(toSet, obj)
				end
			end
			selectionService:Set(toSet)
			selection = selectionService:Get()
		else
			selection = selectionService:Get()
		end
		if #selection == 0 then return end

		--Create a new InputNode with optional settings and make sure input was recieved
		local newName, _ = chain:Node("InputNode"):Set({
			title = "Rename selected object(s)";
			placeholder = "enter a new name...";
			ignoreCase = false;
		}):Get()
		
		if newName then

			--Set waypoint before action to allow undo
			changeHistoryService:SetWaypoint("Renaming Objects")

			--Rename selected objects
			for _, obj in ipairs(selection) do
				obj.Name = newName
			end

			--Set waypoint after action to allow undo
			changeHistoryService:SetWaypoint("Renamed Objects")
		end
	end;

}

Getting started:

After you install the plugin, go to File > Advanced > Customize Shorcuts, search for “cmd+,” and set the shortcut to your preference. A preferred shortcut is the “`” key. After you assign your shortcut, press the key to open up the CMD+ CommandNode that is used to display all of the commands. You will then see a list of all the commands.


Navigation:

For navigation, use your mouse normally or use the up, down, and enter keys for selection. Start typing immediately to give input to the active Node.


Creating custom commands:

To create a new command, add a new folder to game.ServerStorage called “CMD+”. Insert a new modulescript in the folder and copy the template below:

return {

	metadata = {
		id = "";   --unique id for every command; must be different for each cmd
		display = "";   --display-text for command
		shortcut = {}; --optional table of KeyCode Enums
		inputRequired = false;   --enable if command requires input
	};
	
	--main function
	execute = function(chain)

		--create your nodes here

	end;
	
}

See below for API.


Available Nodes:


API:

Before starting, remember to stop the thread if the input from any of the nodes is nil.

Note: the IgnoreCase option, for all the Nodes with this feature, gets saved for each individual Node. This is retrieved the next time the same Node is created.


SelectionNode:

<SelectionNode> chain:Node("SelectionNode") --create SelectionNode

<SelectionNode> SelectionNode:Set({
	title = ""; --title of Node, default: ""
	placeholder = ""; --placeholder text of Node, default: ""
	ignoreCase = false; --disable/enable IgnoreCase option, default: true
})

<table> SelectionNode:Get()
--returns a table of selected objects by user
--returns an empty table if no objects were selected
--yields until input is given
--nil if no input received

InputNode:

<InputNode> chain:Node("InputNode") --create InputNode

<ListNode> ListNode:Set({
	title = ""; --title of Node, default: ""
	placeholder = ""; --placeholder text of Node, default: "";
	default = ""; --optional value for node, default: "";
	list = {
		{"", false, Color3.fromRGB()} --format: {option name, true to show icon, icon color}
	};
})

<string, boolean> InputNode:Get()
--returns a string inputted by the user and a boolean if IgnoreCase was selected
--yields until input is given
--nil if no input received

ChoiceNode:

<ChoiceNode> chain:Node("ChoiceNode") --create ChoiceNode

<ChoiceNode> ChoiceNode:Set({
	title = ""; --title of Node, default: ""
	text = ""; --text display message, default: ""
		choices = {
			{"", ""} --format: {key, text}
	};
})

<string> ChoiceNode:Get()
--returns a string key indicating which choice was selected
--yields until input is given
--nil if no input received

ListNode:

<ListNode> chain:Node("ListNode") --create ListNode

<ListNode> ListNode:Set({
	title = ""; --title of Node, default: ""

	placeholder = ""; --placeholder text of Node, default: ""
	list = {
			{"", true, Color3.fromRGB()} --format: {"key", "icon", "icon color"}
	};
})

<string> ListNode:Get()
--returns a string key indicating which option from the list was selected
--yields until input is given
--nil if no input received

Let me know if you’ve created an epic custom command. I will add your command to the repo for other people to use!

23 Likes

This looks very interesting!

I’m confused on what would be a use case though.

Can you explain some use cases where you would make “custom commands” with these different nodes?

2 Likes

This can be used for any little thing you want to execute quickly. For example, if you want to quickly change the presets of your lighting settings. Or maybe you want to modify certain objects in a way. Any shortcut you can really think of can be implemented using this plugin.

2 Likes

This looks very interesting. I might try it out.

Just one question: Is it exploitable?

2 Likes

Not sure what you mean by exploitable. This plugin is meant to be used only in Studio.

Ohhhhh. My bad. I didn’t read it properly.

It’s a great plugin idea! Definitely got some use to this. I frequently use the output for niche things like renaming different objects and instancing a group of things. This plugin would save a lot of my time! Except…


It’s kind of hard to access.

1 Like

That behaviour is expected since the plugin is meant to activate when you press your preferred shortcut key and be destroyed if your cursor is away from the viewport. To fix this all you have to do is open up File > Advanced > Customize Shorcuts, search for “cmd+,” and set a shortcut of your liking. CMD+ will then work as normal.

Are you able to make it an option where it supports regular clicking and have it not disappear if the mouse isn’t on it?

It’s definitely possible to do that. However, that would defeat the entire purpose of having CMD+ being able to be useful and user-friendly for quick shortcuts. In other words, if you take a look at Blender for example, you can see that its interface works in a similar way. I made CMD+ while keeping in mind of giving the user a similar capability.

Yea I can understand where you are coming from, but imo gui click based methods are also good to have, and I actually prefer them instead of shortcut based interaction methods

In regards to the detailed destroy behaviour of the Node class, along with the ability to interact with the Node with your mouse normally, I think it should give you plenty control over the interface. It might seems like weird behaviour initially, but after using it a couple of times, I have no doubt that you’ll get used to it eventually and actually prefer this type of behaviour. In the end its a matter of personal preference and I just hope this doesn’t cause too big of a problem when using CMD+.

Sometimes I forget new keybinds I’ve set because I’m not quite used to them yet (and yes it’s kind of a me-problem), so I would prefer having at least an option to toggle if you click outside of the widget to close or hover out of the widget to close. This is just to make it more beginner-friendly and the general ease of use.

If you can add this small feature it would be great! Otherwise, I guess I’ll have to try better to not forget my keybinds. Thanks!

Thank you for your suggestion. I will consider adding this feature to help others easily use CMD+.

If any of you guys are unable to code or don’t fully understand how the plugin works, you can just let me know the type of commands and Nodes you want included and I’ll try my best to add it in.

Unfortunately, this seems obsolete for Mac Users since Shortcuts and Automator on the Mac does a better job automating my workflow.

Im not so sure I understand. I don’t use a Mac and I’m not sure what those are. However, this plugin gives you the capability to make custom commands from within Studio. You can create actions like searching for objects and selecting them. I don’t see how external tools would be relevant here.

I 100% agree.

The thing to keep in mind when making a public plugin is user experience. Not everyone is the same as you and me.

If you notice in blender a lot of shortcuts also have a UI button, while using the shortcut is a lot easier for most people. The UI button is there for the people who might forget shortcuts easily or like using their mouse or whatever weird reason they have.

It’s better to have the extra options even if it’s redundant because then it will be accessible to more people.


Suggestion

I don’t know if this is possible, but if you are somehow able to access the shortcut key the user sets the plugin to within your plugin script, you can rename PluginToolbarButton to something like “CMD+ (key_symbol_here)”. That way it can remind people what the shortcut is if someone forgets.

Again, I don’t if this is possible or maybe it’s possible but you would have to reload the plugin anytime the user changes the hotkey.

Just an idea.

Two more suggestions/fixes:

  1. (importance: not-so, quality-of-life fix)
    Whenever CMD+ switches to an InputNode it accepts text without selecting the textbox (which is good) but the cursor thing does not blink until I start typing or select it with mouse (so it’s a little misleading I don’t know if I’m focused on the textbox or not)

  2. (importance: medium, bug)
    Currently if one of my custom commands has an error in it and I try to use it with CMD+, CMD+ crashes and completely stops working. The only way to get it up and running again is to reload it by going to plugin manager and disabling it and then enabling it.

So far I’ve been using it and I like it :+1: , it just needs a little more polishing.


Edit: Just realized there’s a github! I’ll try to fix the issues and make a PR request. This is my first time using Github properly so this is exciting!

Edit2: What’s on github is not the published plugin (I had to get it from the local cache, the one on github is completely broken) Also I can’t publish a pull request. Maybe make another branch and allow users to submit PRs?

Edit3: Figured it out. Made a fork, fixed the issue, and made a pull request from the fork.

¡Esto parece muy interesante!

Sin embargo, estoy confundido sobre cuál sería un caso de uso :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: