Sublime Forum

Snippet Automation

#1

What I’d like to do is select some text, press a key or two, and then that text is loaded into the snippet, for me to save (ideally with the file name automatically generated) so I can just press save.

Let’s pretend that the text I’ve selected is Bob’s Big Banana.

If I select Bob’s Big Banana and then go to Tools => Developer => New Snippet I get:

<snippet>
  <content><![CDATA[
Hello, ${1:this} is a ${2:snippet}.
]]></content>
  <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
  <!-- <tabTrigger>hello</tabTrigger> -->
  <!-- Optional: Set a scope to limit where the snippet will trigger -->
  <!-- <scope>source.python</scope> -->
</snippet>

what I’d like is:

<snippet>
  <content><![CDATA[Bob's Big Banana
]]></content>
  <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
  <!-- <tabTrigger>Bob's Big Banana</tabTrigger> -->
  <!-- Optional: Set a scope to limit where the snippet will trigger -->
  <!-- <scope>source.python</scope> -->
</snippet>

and the file name to be Bob’sBigBannana.sublime-snippet, and just be able to press enter (for the save)

I’ve tried saving a generic snippet:

<snippet>
	<content><![CDATA[${TM_SELECTED_TEXT}]]></content>
	<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
	<tabTrigger>${TM_SELECTED_TEXT}</tabTrigger>
	<!-- Optional: Set a scope to limit where the snippet will trigger -->
	<scope>source.c++</scope>
</snippet>

but when I select, say, Bob’s Big Banana, and use crtl+shift+p to ‘use’ generic.sublime-snippet all i get is ${TM_SELECTED_TEXT} printed where Bob’s Big Banana was.

I’ve tried removing the ${} from TM_SELECTED_TEXT but no difference. I’ve tried logging commands to see what the ‘create new snippet command’ was so the perhaps I could bind it to a keybinding and pass an argument or two to it, but to no avail, it shows all commands expect the one where I create a new snippet.

Can this be done, because if so it would make snippets properly powerful ?

Thanks for your time in reading this.

Lozminda

1 Like

Whether sublime-snippet files can contain multiple template rules
#2

Some progress, having removed the curly brackets for TM_SELECTED_TEXT and just used $TM_SELECTED_TEXT and using ctrl+shift+p I have a snippet that replaces the selected text with itself. But what I want to do I be able to save that as a new snippet file, so I can generate snippets as I go, rather than having to type them out again.
I’m hoping there’s some command like
command: “create_new_snippet”, args “TM_SELECTED_TEXT”, etc etc…

Further stuff part 2: I also have

  {
      "keys": ["super+s", "super+s"],
      "command": "show_overlay",
      "args": {
          "overlay": "command_palette",
          "text": "generic snippet"
      }
  }, 

when I replace generic snippet with new raw snippet (from package dev) I just get a blank new file. In fact new raw snippet doesn’t work at all (but I guess that’s a separate question :yum:)

Cheers :beers: :coffee:

Ps And finally I’ve tried this

  {
      "keys": ["super+s", "super+s"],
      "command": "open_file",
      "args": {"genericSnippet.sublime-snippet":
        "/home/elitebook/.config/sublime-text-3/Packages/User/"
      }
  },

but that doesn’t open the snippet, darn it !

And finally finally:

  {
      "keys": ["super+s", "super+s"],
      "command":"multicommand",
      "args":{
      "commands":[ 
        {"command":"new_file"}, 
        {"command":"open_file",
          "args": {"genericSnippet.sublime-snippet":
        "/home/elitebook/.config/sublime-text-3/Packages/User/"}
      }
    ]
    }
  },
0 Likes

#3

When snippets expand, $TM_SELECTED_TEXT (or ${TM_SELECTED_TEXT}) get replaced with the selected text, yes. What you’ve defined here is a snippet which, when invoked will insert the selected text (but which has a broken tab trigger, requiring you to invoke the snippet from the command palette or via other means).

The command name is new_snippet and it lives in Default/new_templates.py, but it doesn’t take arguments.

The open_file command expected you to provide it an argument named file that tells it what file to open. You provided it an argument named genericSnippet.sublime-snippet. Commands in the core are more forgiving of receiving arguments they don’t expect, or you would have seen an error message in the console about this.

Yes, but I doubt you can pull something like this off with just key bindings and no plugins (unless you’re using the latest build of ST4 and it’s built in chain command); you need to be able to create a file and also insert a snippet into that file, and macros can’t do that because new_file is not a TextCommand. Also there’s not a command that specifies what the name of a new file should be.

import os
import textwrap

import sublime
import sublime_plugin


def reformat(template):
    return textwrap.dedent(template).lstrip()


class SnippetFromSelectionCommand(sublime_plugin.WindowCommand):
    def run(self):
        active = self.window.active_view()
        initial = active.substr(active.sel()[0])

        v = self.window.new_file()
        v.settings().set('default_dir', os.path.join(sublime.packages_path(), 'User'))
        v.settings().set('default_extension', 'sublime-snippet')
        v.assign_syntax('Packages/XML/XML.sublime-syntax')
        v.set_name("%s.sublime-snippet" % initial)

        template = reformat(
            """
            <snippet>
            \t<content><![CDATA[
            {0}.
            ]]></content>
            \t<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
            \t<!-- <tabTrigger>{0}</tabTrigger> -->
            \t<!-- Optional: Set a scope to limit where the snippet will trigger -->
            \t<!-- <scope>source.python</scope> -->
            </snippet>
            """.format(initial))
        v.run_command("insert_snippet", {"contents": template})

    def is_enabled(self):
        active = self.window.active_view()
        return active is not None and len(active.sel()) == 1 and not active.sel()[0].empty()
    { "keys": ["super+t"], "command": "snippet_from_selection",
      "context": [
          { "key": "num_selections", "operator": "equal", "operand": 1 },
          { "key": "selection_empty", "operator": "equal", "operand": false },
      ],
    },

That said, for simple completions like this, snippets are vastly more costly than a sublime-completions file, which is much more light weight and suited to the idea of simple one or two word expansions. Snippets are for weightier pieces of text. Using snippets to insert small phrases is the equivalent of hammering in finishing nails with a 20 pound sledge hammer. :wink:

2 Likes

#4

That is ‘farking’ marvellous ! Thank you. Really really good. Makes life sooo much easier and quicker.

:smiling_face_with_three_hearts: :heart_eyes: :star_struck: :dizzy: :love_you_gesture: :santa: :mermaid: :snowboarder: :unicorn: :unicorn: :unicorn: :pizza: :beer: :rocket:

(I might have over unicorned)

Now all I’ve got to do is sort out my ‘smart_ptr with custom deleter in constructor initialisation list nightmare’ (out of the scope of this forum, darn :crazy_face:!) and I can die a happy man.

Thanks again, wicked !

0 Likes

#5

We’ve (by that I mean largely @OdatNurd) have done a few mods:

    class SnippetFromSelectionCommand(sublime_plugin.WindowCommand):
        def run(self):
            active = self.window.active_view()
            initial = active.substr(active.sel()[0])
            ftList = re.split('[^a-zA-Z]', initial)
    # I'm sure there's a clever while loop that can go here adding elements from ftList onto filename
    # till it's the right length
            tabTrig = ftList[0]
            if len(tabTrig) < 4:
                tabTrig += ftList[1]
            filename = tabTrig
            if len(tabTrig) > 8:
                tabTrig = tabTrig[0:8]

            scope = active.scope_name(active.sel()[0].begin()).split()[0]

            v = self.window.new_file()
            v.settings().set('default_dir', os.path.join(sublime.packages_path(), 'User'))
            v.settings().set('default_extension', 'sublime-snippet')
            v.assign_syntax('Packages/XML/XML.sublime-syntax')
            v.set_name("%s.sublime-snippet" % filename)
    # I removed a tab or 2 from the template,  just personal choice
            template = reformat(
                """
                <snippet>
                <content>
                <![CDATA[
                {0}
                ]]></content>
                \t<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
                \t<tabTrigger>{2}</tabTrigger>
                \t<!-- Optional: Set a scope to limit where the snippet will trigger -->
                \t<scope>{1}</scope>
                </snippet>
                """.format(initial, scope, tabTrig))
            v.run_command("insert_snippet", {"contents": template}) 

That’s a cool design, it would seem that the template can be reformatted with various options as demarked by {0-number of args passed to reformat}, eg {0},{1},{2}.

0 Likes

#6

In PHP if I select $snippets = glob($dir . '/*.sublime-snippet'); it will result = glob( . '/*.sublime-snippet'); I believe this is because format function: format(initial, scope, tabTrig))

0 Likes