Prepend or append text to selected lines

I saw this action pop up in the directory lately. Sorry, it’s anonymous, so can’t credit anyone.

https://actions.getdrafts.com/a/163

I was just wondering if someone has already built an action similar, but as an extension to the above, you could select in the prompt if you wanted to append or prepend inserted text, and also, have it act on only selected lines in the draft - all lines if no selection made.

As I say, it seems like a handy action to have and I couldn’t find anything out there. No worries if not.

FYI, this is something pretty easily done with regular expressions in Find and Replace. If you enable “use regular expressions” in Find, and use this find, you are finding the line beginning, and the contents of the line become the first capture group, which can be used in the replace value by inserting \1.

So, doing this and “replace all”:

Find: ^(.*)
Replace: Prefix-\1

Would turn the draft:

1
2
3

Into:

Prefix-1
Prefix-2
Prefix-3

Thanks. That’s handy, but I’m not likely to remember it (regex not my thing), whereas an action can be named and easily run.

I’ve been thinking about this for while. It’s similar to other Drafts actions I’ve written, so I took some of that code and built this.

It’s just one JavaScript step with this script:

// Append or prepend the selected lines (or all lines) with
// text supplied by the user.

// Prepare the prompt
var p = Prompt.create();
p.title = "Append/Prepend"
p.message = "Text to add to start or end of lines."
p.addTextField("text", "", "", {placeholder: "Added text", wantsFocus: true});
p.addButton("Prepend");
p.addButton("Append");

// Define the text that's going to be edited
// If there's no selection, use the whole draft
var selRange = editor.getSelectedRange();
if (selRange[1] == 0) {
	editor.setSelectedRange(0, editor.getText().length);
}
// Extend the text out to the beginning of the first
// selected line and the end of the last
var lnRange = editor.getSelectedLineRange();
var lnText = editor.getTextInRange(lnRange[0], lnRange[1]);
// Don't include trailing linefeeds
if (lnText.slice(-1) == "\n") {
	lnRange[1] -= 1;
	lnText = lnText.slice(0, -1);
}

// Get the text to be added from the user
var didSelect = p.show();

if (didSelect) {
	// Edit a copy of the chosen text
	var addText = p.fieldValues["text"];
	var lines = lnText.split('\n');
	var edited = [];
	
	if (p.buttonPressed == "Prepend") {
		lines.forEach(line => edited.push(addText + line));
	}
	else {
		lines.forEach(line => edited.push(line + addText));
	}
	
	// Replace the text in the editor
	editor.setTextInRange(lnRange[0], lnRange[1], edited.join('\n'));
}

What bothers me about it is the rigamarole in the “Define the text that’s going to be edited” section. The goals are:

  1. If nothing is selected, apply the edit to the entire draft.
  2. If there’s a selection, apply the edit to the lines that are selected. Extend the selection out to encompass entire lines if it doesn’t already.
  3. Handle the possibility of a linefeed at the end of the selection. If there is, the split('\n') that comes later will leave us with a blank line that we don’t want to include in the text that gets edited.

The code handles all of this, but I keep thinking there’s a simpler way to do it. Any suggestions?

I’m not sure this is really an improvement. You have multistep logic, so you have to walk through each of those decision points…but I’d probably write more like this:

let st, len;
if (editor.getSelectedRange()[1] > 0) { // use selected lines
	[st, len] = editor.getSelectedLineRange();
}
else { // use full text
	[st, len] = [0, editor.getText().length];
}
let lnText = editor.getTextInRange(st, len);
while (lnText.endsWith("\n")) {
	lnText = lnText.slice(0, -1);
	len -= 1;
}

:+1: Definitely cleaner, with fewer gets and eliminating my unnecessary set. Also, separate variables for st and len make more sense than an array, as they’re always used individually.

I think I’ll change the while back to an if, because if there are a bunch of linefeeds at the end, the user probably does want them edited.

1 Like

I’m going to put this question here for continuity, even though we’re moving away from the original request.

I’ve changed the last lines of my script to

	// Replace the text in the editor
	var rep = edited.join('\n')
	editor.setTextInRange(st, len, rep);
	editor.setSelectedRange(st, rep.length);
}

The intent is to select the replacement text in case I want to do something with it right away (like copy it), something I’ve done in other actions. But it doesn’t work here. Is it because of the prompt (my other scripts don’t have that)?

Also, when this action finishes, I can’t undo the append/prepend change. Again, in similar actions (like this one), undo works. What’s the difference?

I have a sense these two problems are related, but I don’t know why or what to do about it.

UPDATE:
Never mind, I found it. The problem was that the editor lost focus when the prompt came up. I need to give it back with editor.activate().

Great stuff. Many thanks for sharing.

I’ve felt for a long time this sort of thing would be very nicely done with the ability to run macros against selected lines in “line/rearrange mode” (Or whatever it’s called.)