I don't think there needs to be an undo for copying files (cp, ditto), because you can simply delete the copy, if you did something wrong; whether you undo the copying or delete the copy, it both results in one additional user action, the only difference being that CMD-Z is kind of a universal keyboard shortcut you don't need to learn anymore. (But so is CMD-BACKSPACE.
)
Undo is good for moving files (mv), and the latter would automatically include not only Move, but also Rename or Rename in Place (which are both the same as Move), move to trash, and standard keyboard operations like CMD-C and ALT-CMD-V, the macOS way of doing CMD-X + CMD-V (Cut & Paste). [Which, by the way, makes me wonder: in Nimble Commander there is a Cut command (CMD-X), but it's always greyed out; imho the Cut menu item could be totally eliminated, because we have the Move functionality, and the above-mentioned (safe!) macOS way with CMD-C + ALT-CMD-V.]
In my view there shouldn't be an Undo for Delete & Delete Permanently, which is the NC function corresponding to rm -rf, right? If you choose to actually delete (remove) a file for real, and you've also passed the safety prompt, there shouldn't be an undo for that, because it's a destructive process/command. (Undoing this would imho be quite complicated, because you would have to recreate the file at the original path from the unlinked file contents, which HFS obscures by design.) However, just moving a file/directory to the trash is fine for undo, because it's a move command (see above), and .Trash is just a directory on your volume, only hidden, not fully obscured.
How to do it? I'm not a programmer, I just do some little shell scripts here and there, but my gut tells me
database, e.g. sqlite. In Preferences the user should be able to set the maximum amount of undo steps, and then you just add (or remove) the necessary amount of rows in the undo.db. Then NC only needs the file's/directory's original path and its new path (which NC has anyway), and needs to write those into the "old" and "new" columns in the undo.db's next empty row. If there is no empty row, contents of row no. 1 are deleted for * (all columns), and the contents of rows 2->n are moved up to rows 1->(nā1), and row n receives the current operation's new old & new paths in the respective columns. In case of a user's undo command, NC would simply access the undo.db, read the contents of the old & new column in the last row that has no (null) content, internally parse the whole row contents into new and old path, internally select the file/dir at the new path, and then move that to the old path, then upon success delete the contents of the respective row in undo.db. Seems kinda simple to me, actually.
Three possible problems:
(1) for security reasons the database should be encrypted, so Preferences would need a button, which would enable the user to rebuild the database in case it becomes corrupted (like the VACUUM command in sqlite).
(2) When bulk moving many paths to new paths at once, e.g. 1000 files from one directory to another, that operation should only occupy one column in the undo.db, not 1000. (!) So NC shouldn't write paths to the undo.db rows as default strings, but as arrays, which would result in one additional parse, if the user asks NC for an undo.
(3) Nimble Commander should always wait for the user's final action. If he hits CMD-C, but then CMD-V next, it's a copy command (or duplication, if it's in the same directory), and undo isn't necessary; if he hits ALT-CMD-V next, it's a move command, and then the two paths (path arrays) are written into undo.db.
To speed up the process, the last move operation's paths (path arrays) could be kept in active memory, not written to disk. Only when the user issues the next move command on a file/dir, then NC should write the previous paths (path arrays) to disk (to the undo.db). There should be a limit on the array size, though; anything too big should be written to disk immediately, either to a tmp file or directly into the undo.db.
(PS: I can't say if undo should be applied to synchronized folders.)
EDIT: There would also need to be a redo database, so the database file should have two tables, one "undo", one "redo", each with two columns, "old path" and "new path", and each with as many columns as the number of undo steps the user has specified. Then, if the user calls Undo (CMD-Z), the paths (path arrays) from the undo table are moved over to the redo table in the same database, and when the user calls Redo (SHIFT-CMD-Z), then the values are moved again from the redo table to the undo table. (And so on.)