spacemod
spacemod is a text search-and-replace tool optimized towards refactoring
code.
# example.py (before)
copy_file(to_file=to_file, from_file=from_file)
copy_file(
to_file=get_file(filepath, mode),
from_file=get_file_writer(other_filepath, other_mode)
)
# Use spacemod's custom pattern-matching language to deal with whitespace easier.
# Without -S, normal regex patterns are assumed.
$ spacemod -S \
'copy_file ( to_file= (.*) , from_file= (.*) )' \
'copy_file($2, $1)' \
example.py
# spacemod will open interactive TUI and ask for approval of diffs. Use
# --accept-all to use spacemod non-interactively.
# example.py (after)
copy_file(from_file, to_file)
copy_file(get_file_writer(other_filepath, other_mode)
, get_file(filepath, mode))
It is very similar to fastmod, but with some additional features:
- Undo stack. Approved a diff too soon? Hit
[u]ndoto revert. - Yes to all diffs like this. Auto-approve future diffs with the exact same content.
- Parenthesis-matching (experimental). Besides regex, spacemod also supports a custom regex-like language that requires less escaping and whitespace-handling.
- Replace recently edited files first. Spacemod tries to show files with higher mtime first, because they are more likely to be relevant to what you are working on.
Installation
Download the latest binary from Codeberg releases or use cargo install spacemod.
Documentation
Check the website for additional docs.
License
Licensed under MIT, see ./LICENSE.
Matching modes
By default, your pattern is interpreted as a regular RE2-style regex using regex crate.
spacemod provides two flags to change that:
-Sto use parenthesis-matching and implicit whitespace, a.k.a "space mode".-Fto interpret the pattern as a literal string to find, same as-Fin fastmod.
-S requires further explanation. Let's motivate it with an example:
#![allow(unused)] fn main() { vec!["foo".to_string(), "bar".to_string(), "baz".to_string()] }
You get the terrible idea of changing all of your x.to_string() calls to
String::from(x) instead.
In fastmod, or any regex-based search-and-replace, you'd write:
fastmod '"(.*)"\.to_string\(\)' 'String::from("$1")'
You forgot about the greediness of .* and should've maybe written .*?
instead. Now your code looks like:
#![allow(unused)] fn main() { vec![String::from(String::from(String::from("foo"), "bar"), "baz")] }
Let's try that again. spacemod lets you write:
spacemod -S '" (.*) " \.to_string ( )' 'String::from("$1")'
The correct end result looks like:
#![allow(unused)] fn main() { vec![String::from("foo"), String::from("bar"), String::from("baz")] }
Spacing out parenthesis and quotes tells spacemod that those tokens, "" and ():
- must be balanced wherever they appear in a match
- can have surrounding whitespace (including newlines)
- are literals (fewer backslashes!)
As a result of implicit whitespace, spacemod would have also found all matches in the following code:
#![allow(unused)] fn main() { " foo".to_string( ) "foo" .to_string() }
Syntax reference
spacemod's pattern syntax consists of tokens delimited by a single space each.
A token can either be a parenthesis/quote, or a substring of a regex:
{ regex1 } regex2 { regex3 }
If you need to use a literal space anywhere in a regex substring, escape it as
\ . That also means escaping a regex like \ as \\ . Backslashes followed
by anything but a space within a regex do not need to be escaped.
spacemod knows the following parenthesis/quotes:
{}[]()<>"", ``, ''. Since start and end are the same, this basically devolves into asserting there is an even number of tokens.
You can extend this list with -p ab where a is the opening parenthesis, and
b the closing counterpart. See --help for more information.
Alternatives
You may find the following tools useful if spacemod is doing too much or too little for you. The primary focus of this list is on editor/IDE-independent tools, and preferably on those which can be composed into more complex shell-scripts.
-
spacemodis heavily inspired by fastmod and was specifically built to deal with shortcomings of regex.fastmodis much faster. -
ast-grepis a very easy to use AST-based code search tool. -
combyactually has grammars built-in for various filetypes to understand what wildcards are supposed to match in which contexts. It appears to be "a step up" fromspacemodthe same wayspacemodsyntax is a step up from regex. That goes for both expressiveness and complexity. -
codemodis another search-and-replace tool that has language-specific knowledge. It supports both basic regex replacements and more sophisticated transformations written in Python. -
spatchfrom the PFFF-suite appears to be very similar tocomby. -
gritqlseems similar toast-grep.
Other tools in the same space
- Beyond Grep has a table of
(regex-based) text search tools. The best one IMO is
ripgrep.