Sublime Forum

How do I call a custom command from Context Menu

#1

I have a custom command I’ve created (in Packages\User\Default.sublime-commands). It appears in the command palette and runs successfully.

I can duplicate the code and put it in my context menu file (Packages\User\Context.sublime-menu) and have it appear there as well, but surely there’s a way not to have to have duplicate code and arguments in two locations. I’ve googled, but can’t find anything. So…

How can I call my custom command from the Context.sublime-menu file?

My custom command (as it exists in the above-mentioned .sublime-commands file) is a call to the RegReplace plugin…

{ "caption": "RegReplace - CFML to CfScript" , "command": "reg_replace" , "args": { "replacements": "remove_spaces_before_and_after_solo_slashes" , "convert_cfset_tags_to_cfscript_slash" , "convert_cfset_tags_to_cfscript_noSlash" , "convert_cflocation_tags_to_cfscript_slash" , "convert_cflocation_tags_to_cfscript_noSlash" , "convert_comments_area_start" , "convert_comments_area_end" , "remove_cfscript_tag_line" , "remove_cfscript_tag_shared_line" ] , "find_only": false } }

I’ve actually got about 3 times more arguments (in the replacements node) than this, which is why I really want this code to exist in just one place.

0 Likes

#2

Yes, there is a way. You have to write your own plugin command that runs the actual command (reg_replace) with these arguments.

Other than that, no. Command arguments are unreferencable so unless they are “cached” by the plugin command and allows you to reference these by a short name (which the RegReplace command already does, since “replacements” are just references to something in the settings).
I suggest creating an issue at the regreplace package repo requesting functionality to cache a list of replacements and thus being able to get a shorthand argument that is equivalent to calling the command with the entire replacements list.

0 Likes

#3

A simple TextCommand extension for something like this is pretty simple, and in cases where you may want more than one way to run it (pallette, keystroke etc.) does not seem an unreasonable workaround.

0 Likes

#4

Thanks for recommending the TextCommand plugin style. After some googling, and a few days of work (considering this is also my first foray into python), I was able to cobble together a plugin that works, based on some examples I found online.

For those coldfusion developers out there, this one converts many popular commands from CFML to cfScript style, especially good for converting CFCs.

However, it could easily be used to do other bulk find/replace operations.

My next idea is to split it up somehow so that I can do multiple kinds of replaces, instead of just having the one in the “run()”. I’m guessing I need to figure out how to use arguments, and pass one in when I call it.

[code]import sublime, sublime_plugin, re

Extends TextCommand so that run() receives a View to modify.

class dgReplaceLibCommand(sublime_plugin.TextCommand):
def run(self, edit):
view = self.view

	arrRE =  
		" \t]*(/?)> \t]*$", "\\1>"]
		, "<cfset \\t]+(.*)/>$", "\\1;"] 
		, "<cfset \\t]+(.*)>$", "\\1;"] 
		, "<cfreturn \\t]+(.*)/>$", "return \\1;"] 
		, "<cfreturn \\t]+(.*)>$", "return \\1;"] 
		, "<cflocation \\t]+(.*)/>$", ",, location( \\1 );"] 
		, "<cflocation \\t]+(.*)>$", ",, location( \\1 );"] 
		, "<cfcatch \\t]+type=\"(^\"]*)\" \\t]*>$", "} catch ( \\1 e ) {"] 
		, "<cfcatch \\t]*>$", "} catch ( NoTypeSpecifiedSoFixIt e ) {"] 
		, "^( \\t])*</cfcatch> \\t]*\\n", ""] 
		, "<cfdump \\t]+(.*)/>$", ",, writeDump( \\1 ) {"] 
		, "<cfdump \\t]+(.*)>$", ",, writeDump( \\1 ) {"] 
		, "<cfswitch \\t]+expression=(.*)>$", "switch( \\1 ) {"] 
		, "</cfcase> \\t]*$", "break;"] 
		, "</cfswitch> \\t]*$", "}"] 
		, "<cfcase \\t]+value=(.*)>$", "case \\1: // put a list of values as separate case statements"] 
		, "<cfdefaultcase> \\t]*", "default:"] 
		, "</cfdefaultcase> \\t]*", "break;"] 
		, "<cfif \\t]+(.*)>$", "if (\\1) {"] 
		, "<cfelseif \\t]+(.*)>$", "} else if (\\1) {"] 
		, "<cfelse \\t]*> \\t]*$", "} else {"] 
		, "</cfif> \\t]*$", "}"] 
		, "<cfinclude \\t]+template=(.*)/>$", "include \\1;"] 
		, "<cfinclude \\t]+template=(.*)>$", "include \\1;"] 
		, "<cftry> \\t]*", "try {"] 
		, "</cftry> \\t]*", "}"] 
		, "<cfabort \\t/]*> \\t]*", "abort;"] 
		, "<!--- \\t]*(.*) \\t]*---> \\t]*$", "// \\1"] 
		, "^( \\t]*)<!--- \\t]*$", "\\1/*"] 
		, "^( \\t]*)---> \\t]*$", "\\1*/"] 
		, "^ \\t]*</?cfscript> \\t]*\\n", ""] 
		, "</?cfscript>", ""] 
	]
	for list in arrRE:
		# print(list[0] + list[1])
		thisFind = list[0]
		thisReplace = list[1]
		reobj = re.compile(thisFind, re.MULTILINE)

		for region in self.selections(view):
			trimmed = reobj.sub(thisReplace, view.substr(region))
			view.replace(edit, region, trimmed)

def selections(self, view, default_to_all=True):
	regions = [r for r in view.sel() if not r.empty()]
	if not regions and default_to_all:
		regions = [sublime.Region(0, view.size())]
	return regions[/code]
0 Likes

#5

Wow, it’s so hard to find answers for plugin development! Especially for a python newb like me. But I fuddled my way through moving the set of replaces into another function so that I can easily add another set later.

caveat: I’m a newb here, so there may be ways to do it better.

One of the things that was hard to find out was that you don’t pass the same arguments when you take some code out into a separate function. My new function got “self” and “edit” as arguments (line 20), but… when I passed “self” and “edit” when I called “cfml_to_cfscript” (at about line 8), that gave me an error. It worked when I just passed “edit” only. I guess “self” is implied or something.

Here’s the command I put in the “Packages\User\Context.sublime-menu” file:

[code]{
“id” : “dgReplaceLib”,
“caption”: “dg Replace Library”,
“children”:

	{
		"id": "dgReplaceLib_cfml_to_cfscript",
		"caption" : "CFML to cfScript",
		"command" : "dg_replace_lib",
		"args" : { "replaceSet": "CFML to cfScript" }
	}
]

}[/code]

And here’s the plugin code:

[code]import sublime, sublime_plugin, re

Extends TextCommand so that run() receives a View to modify.

class dgReplaceLibCommand(sublime_plugin.TextCommand):
def run(self, edit, **args):

	if (args'replaceSet'] == "CFML to cfScript"):
		self.cfml_to_cfscript(edit)



def selections(self, view, default_to_all=True):
	regions = [r for r in view.sel() if not r.empty()]
	if not regions and default_to_all:
		regions = [sublime.Region(0, view.size())]
	return regions



def cfml_to_cfscript(self, edit):
	view = self.view

	arrRE =  
		" \t]*(/?)> \t]*$", "\\1>"]
		, "<cfset \\t]+(.*)/>$", "\\1;"] 
		, "<cfset \\t]+(.*)>$", "\\1;"] 
		, "<cfreturn \\t]+(.*)/>$", "return \\1;"] 
		, "<cfreturn \\t]+(.*)>$", "return \\1;"] 
		, "<cflocation \\t]+(.*)/>$", ",, location( \\1 );"] 
		, "<cflocation \\t]+(.*)>$", ",, location( \\1 );"] 
		, "<cfcatch \\t]+type=\"(^\"]*)\" \\t]*>$", "} catch ( \\1 e ) {"] 
		, "<cfcatch \\t]*>$", "} catch ( NoTypeSpecifiedSoFixIt e ) {"] 
		, "^( \\t])*</cfcatch> \\t]*\\n", ""] 
		, "<cfdump \\t]+(.*)/>$", ",, writeDump( \\1 ) {"] 
		, "<cfdump \\t]+(.*)>$", ",, writeDump( \\1 ) {"] 
		, "<cfswitch \\t]+expression=(.*)>$", "switch( \\1 ) {"] 
		, "</cfcase> \\t]*$", "break;"] 
		, "</cfswitch> \\t]*$", "}"] 
		, "<cfcase \\t]+value=(.*)>$", "case \\1: // put a list of values as separate case statements"] 
		, "<cfdefaultcase> \\t]*", "default:"] 
		, "</cfdefaultcase> \\t]*", "break;"] 
		, "<cfif \\t]+(.*)>$", "if (\\1) {"] 
		, "<cfelseif \\t]+(.*)>$", "} else if (\\1) {"] 
		, "<cfelse \\t]*> \\t]*$", "} else {"] 
		, "</cfif> \\t]*$", "}"] 
		, "<cfinclude \\t]+template=(.*)/>$", "include \\1;"] 
		, "<cfinclude \\t]+template=(.*)>$", "include \\1;"] 
		, "<cftry> \\t]*", "try {"] 
		, "</cftry> \\t]*", "}"] 
		, "<cfabort \\t/]*> \\t]*", "abort;"] 
		, "<!--- \\t]*(.*) \\t]*---> \\t]*$", "// \\1"] 
		, "^( \\t]*)<!--- \\t]*$", "\\1/*"] 
		, "^( \\t]*)---> \\t]*$", "\\1*/"] 
		, "^ \\t]*</?cfscript> \\t]*\\n", ""] 
		, "</?cfscript>", ""] 
	]
	for list in arrRE:
		# print(list[0] + list[1])
		thisFind = list[0]
		thisReplace = list[1]
		reobj = re.compile(thisFind, re.MULTILINE)

		for region in self.selections(view):
			trimmed = reobj.sub(thisReplace, view.substr(region))
			view.replace(edit, region, trimmed)

[/code]

0 Likes

#6

Indeed, self is implied - kind of. When you access a method of an instance (in your case self.cfml_to_cfscript) that method will get self (=the instance whose method you are calling) auto-inserted as the first argument. On the contrary, accessing the method as a class attribute (dgReplaceLibCommand.cfml_to_cfscript) will not auto-insert the first argument since there is nothing to insert. This allows you to do something like dgReplaceLibCommand.cfml_to_cfscript(self) to call a method explicitly with an object that may or may not actually be an instance of dgReplaceLibCommand. It’s of course not safe to do since the code in the method may access attributes of the object you passed that you didn’t define, but that’s the general principle.

Additionally, you may define special methods using the classmethod and the staticmethod built-in functions, preferrably via decorator (@classmethod \n def a_classmethod(cls):, cls gets a reference to the class). I suggest you read up on Python a bit and learn some of these if you are interested in it.

By the way, there will be a plugin development guide in the unofficial docs (which were linked earlier) at some point, but it will take some more time as we need to write it first. :wink: It won’t be a Python tutorial however.

0 Likes