Sublime Forum

Macros don't record Find/Replace?

#55

This was a horrible decision. Ideally, macros should be able to to record and replay any command available vie menues or hot-keys. Maybe this could be solved through scripting but I agree with others that this is overkill. Plus, I don’t want to learn a new scripting language, preferring to use SED from shell.

I switch to Notepad++ on windows when I need macros and have just downloaded Textmate II to my personal Mac book pro and use a third option on Linux. I’d prefer to use the same editor across all my machines regardless of operating system.

I’m uninstalling Sublime on all my plateforms. Sorry guys, you should really reconsider your choice.

Don Rau
Clearwateranalytics
Boise Idaho

0 Likes

#56

I still live in hope that a ‘record and playback anything’ macro facility will find its way into Sublime. It’d probably require some heavy lifting under the hood, because macros are presently implemented more or less for batching TextCommands together, an early part of Sublime’s internal design. Those unfamiliar with ST’s Python API won’t be clear on what macros can and can’t do - and there’s plenty they can’t.

Ideally, at least anything that affects the state of a buffer should be recordable into a macro. That includes cursor movements, S&R, indentations, selections etc… There might be a few exceptions, one example being code folding.

Before Sublime, I used Brief and then Crisp, both of which had ‘record everything, no-brainer’ macro facilities which could be either learnt or hand-coded. For a number of editing situations I still need to fall back to other editors because I can’t mirror a repeat a sequence of actions in a macro, and going the route of building a Python extension is the proverbial sledgehammer.

With full featured macros, effortless huge file support, proper column selection and syntax aware folding added to the core, I could quite see Sublime being the first and last editor I would ever need, except maybe vim for ssh sessions. Further, I think that would expand its reach and sales beyond coders and towards anyone who needs a robust, swiss army knife editor with native performance in their technology workflow. Just my 2p.

0 Likes

#57

I would also like to see find (and replace) in Macros, but a workaround is to copy/paste to a different text editor (notepad++ or emacs), use their well developed macros, then copy/paste the result back to the sublime text editor. clunky but it works for me, and I can use the powerful emacs macros

0 Likes

#58

Deathaxe python script above totaly resolves this, and in addition gives a great way forward for more ST automation.
So if, like me, you came here looking for a solution - scroll up and use it. no need for a new ST feature.

For the record - here is how you use it from within a macro:
{
“args”: {
“pattern”: “^whatever you want(ed)?”,
“replace_by”: “What you need\1”
},
“command”:“find_replace”
}

0 Likes

#59

Hey @deathaxe,
your script runs fine at first look.
When using a “.” as the search/find pattern I discovered strange beaviour, killing parts of the text expected to be the remains.

Adding the flag “LITERAL”, limit the code now to replace almost all occurences of “.” but still killing some text at the start of the orginal.

Finally I discovered that you swapped two arguments in the line with the “find()” function.

in your code change the line after the “while True:” statement

from

        found = self.view.find(pattern, self._flags(flags), start_pt)

to

       found = self.view.find(pattern, start_pt, self._flags(flags))

and you are fine.

@deathaxe please edit your example above if possible!

Thanks for you work!

0 Likes

#60

Good catch. Unfortunatelly I looks like I can’t edit the post anymore.

So here’s the fixed version:

import sublime
import sublime_plugin


class FindReplaceCommand(sublime_plugin.TextCommand):
    """The implementation of 'find_replace' text command.

    Example:

        view.run_command(
            "find_replace", {
                "pattern": "the",
                "replace_by": "THE",
                "start_pt": 100,
                "flags": ["LITERAL"]
            }
        )
    """

    FLAGS = {
        "LITERAL": sublime.LITERAL,
        "IGNORECASE": sublime.IGNORECASE
    }

    def run(self, edit, pattern, replace_by, start_pt=0, flags=[]):
        """Find and replace all patterns.

        Arguments:
            edit (sublime.Edit):
                The edit token used to undo this command.
            pattern (string):
                The regex pattern to use for finding.
            replace_by (string):
                The text to replace all found words by.
            start_pt (int):
                The text position where to start.
            flags (list):
                The flags to pass to view.find()
                ["LITERAL", "IGNORECASE"]
        """
        while True:
            found = self.view.find(pattern, start_pt, self._flags(flags))
            if not found:
                return
            self.view.replace(edit, found, replace_by)
            start_pt = found.begin()

    def _flags(self, flags):
        """Translate list of flags."""
        result = 0
        for flag in flags:
            result |= self.FLAGS.get(flag, 0)
        return result
0 Likes

Variable substitution ignoring `g` flag?
#61

hi @deathaxe
when I try to replace \n with ',\n', then this code will run forever, because from start point begin() it will find the same \n . How about start_pt = found.begin() + len(replace_by) ? what do u think ?

1 Like

#62

For the deathaxe script, is there a way to use backreferences in replacements? The self.view.replace method takes the replacement as a string and and says nothing more about it as far as I can tell.

Taking the example from omri’s post, "replace_by": "What you need\1" for me just causes an error for bad escaping. Escaping the backslash makes the .sublime-macro valid, but the replace then uses literal backslashes.

Not doable? Am I missing something? Working example?

0 Likes

#63

Changelog (Build 4107) 20 May 2021
Text Commands

  • Macros now record Find commands

Nice - Thanks! … but with a macro such as:

F3 (Find Next)
Ctrl+Shift+K (Delete Line)

… when nothing is found, the current line is deleted for each Playback without notice until you realise what’s happening :face_with_raised_eyebrow:

Ctrl+Z, Ctrl+Z, Ctrl+Shift+Z :grimacing:

0 Likes

#64

Macros are plain lists of commands being executed after each other. There’s no control flow to stop in the middle of anywhere if something strange happens.

0 Likes

#65

I accept most of the blame :wink: . I could have put the find at the end and done the first F3, manually:

Ctrl+Shift+K (Delete Line)
F3 (Find Next)

I notice there is the usual feedback on the status bar ("Unable to find ") but, by that time, the later operations could need undoing.

But I wonder if this is an implementation consideration.
Clearly, the team originally excluded Find from macros for a reason (perhaps this reason?).
I’m cautious about trying to anticipate a user’s intentions but, surely, terminating macro playback at the point of “Unable to find” would be no worse than excluding Find operations altogether (prior to ST4)?

Either that or provide a “Find or Halt macro” checkbox in the Tools menu (Macro section) or a separate keybinding (Ctrl+Alt+F3 ?) for “Find or Halt macro playback” (Ctrl+Alt+Shift+F3 = reverse Find ?).

I hope someone can prove me wrong by providing any useful case where a macro contains a Find followed by any operation(s) not dependent on Find success. Then, I’ll shut up :relaxed:

0 Likes

#66

@deathaxe. It is a great Plugin, I can’t get the error @acsr gets:

Finding and replacing “.” with “#FULLSTOP#” turns all the fullstops into #FULLSTOP#. However that’s in code not written text.
Perhaps I’m missing something and that’s not to say that the error doesn’t exist, not at all. Is it worth changing the plugin I wonder ?

Maybe it is, how would I implement the above plugin so that it just does find and replace for seleted text only. If you can point me in the right direction, I’m having a crack at learning Python (should be easy after C++ :crazy_face:) so no need to do all the heavy lifting :dizzy:

In summary only in an area bounded by Selected_Text does the find and replace occur, leaving the character matches outside the area untouched. Hint only required (though a large hint would be great) !

Thanks again…

0 Likes

#67

The original plugin above is a quite dump straight forward wrapper for the view.replace() API function. It can be improved by several means, of course.

According to your description I came up with the following proposal:

The start_pt argument is removed as the command uses selections to look for regions to replace tokens within. If all selections are empty, view.replace() is performed within the whole document. Otherwise only tokens within a non-empty selection are replaced.

It provides a selector argument which can be used to replace tokens only if they match certain scopes. A "selector": "text.html.markdown - markup.raw - source" for instance would replace a token in a Markdown file but leave all fenced codeblocks untouched.

class FindReplaceSelectedCommand(sublime_plugin.TextCommand):
    """The implementation of 'find_replace_selected' text command.

    Example:

        view.run_command(
            "find_replace", {
                "pattern": "the",
                "replace_by": "THE",
                "selector": "text - source",
                "flags": ["LITERAL"]
            }
        )
    """

    FLAGS = {
        "LITERAL": sublime.LITERAL,
        "IGNORECASE": sublime.IGNORECASE
    }

    def run(self, edit, pattern, replace_by, selector=None, flags=[]):
        """Find and replace all patterns.

        Arguments:
            edit (sublime.Edit):
                The edit token used to undo this command.
            pattern (string):
                The regex pattern to use for finding.
            replace_by (string):
                The text to replace all found words by.
            selector (string):
                The selector to match a found token against.
                Can be used to replace tokens of certain scope only.
            flags (list):
                The flags to pass to view.find()
                ["LITERAL", "IGNORECASE"]
        """
        view = self.view
        selections = view.sel()
        selection_empty = all(len(sel) == 0 for sel in selections)

        for found in reversed(view.find_all(pattern, self._flags(flags))):
            # skip all tokens which don't match the selector
            if selector and not view.match_selector(found.begin(), selector):
                continue
            # skip all tokens not within any selection if non-empty selections exist
            if selection_empty or any(sel.contains(found) for sel in selections):
                view.replace(edit, found, replace_by)

    def _flags(self, flags):
        """Translate list of flags."""
        result = 0
        for flag in flags:
            result |= self.FLAGS.get(flag, 0)
        return result
0 Likes

Variable substitution ignoring `g` flag?
#68

Thank you. I’ll get back to you a couple of weeks, got a busy week so have to crack on with other things ATM.

But thanks again, it’s a great plugin !

0 Likes

#69

You are welcome.

0 Likes

#70

Sublime Text 4 now supports Find commands in macros.

0 Likes

#71

@Raikkonen, I’ve got build 4126 but find commands seem still to be not recorded, are they? What should a find command look like in a macro syntax?

Any help on this matter is appreciated!

1 Like

#73

We don’t need Plugins. Here is the sublime text macro code to find “War” and repalce it with “Love”.
[
{
“args”: {“to”: “bof”}, “command”: “move_to”
},
{
“args”: {“characters”: “War”}, “command”: “insert”
},
{
“args”: {“extend”: true, “to”: “bol”}, “command”: “move_to”
},
{
“command”: “slurp_find_string”
},
{
“args”: {“characters”: “Love”}, “command”: “insert”
},
{
“args”: {“extend”: true, “to”: “bol”}, “command”: “move_to”
},
{
“command”: “slurp_replace_string”
},
{
“args”: null, “command”: “left_delete”
},
{
“args”: {“close_panel”: true }, “command”: “replace_all”
},
]

1 Like

#74

Edit /Packages/RegReplace/reg_replace.sublime-settings (you can access this in ST2 at Preferences > Package Settings > Reg Replace > Settings – Default). Create new entries in the “replacements” object, each one being a single regex find/replace. Note that you have to doubly escape special characters.

String those individual replacements together into a command for the Command Palette. To do this, edit the file /RegReplace/Default.sublime-commands (Preferences > Package Settings > Reg Replace > Commands – Default). Add something like this:

0 Likes

#75

That’s really clever!
I was looking for a way to change the search buffer, and this does just that.
Thank you so much!

0 Likes